|
1 | 1 | {-# LANGUAGE NamedFieldPuns #-} |
2 | 2 | module Data.Source.Spec (spec, testTree) where |
3 | 3 |
|
4 | | -import Data.Char (chr) |
5 | 4 | import Data.Range |
6 | 5 | import Data.Source |
7 | 6 | import Data.Span |
8 | 7 | import qualified Data.Text as Text |
9 | 8 |
|
10 | | -import Data.Functor.Listable |
11 | 9 | import Test.Hspec |
12 | | -import Test.Hspec.LeanCheck |
13 | | -import Test.LeanCheck |
14 | 10 |
|
15 | | --- This file deals with Range values, which is unfortunate because |
16 | | --- Hedgehog has its own Range type. We solve this by importing |
17 | | --- everything qualified. |
18 | 11 | import qualified Generators as Gen |
| 12 | +import qualified Hedgehog.Gen as Gen |
19 | 13 | import Hedgehog ((===)) |
20 | 14 | import qualified Hedgehog.Range |
21 | | -import qualified Hedgehog |
| 15 | +import Hedgehog hiding (Range) |
22 | 16 | import qualified Test.Tasty as Tasty |
23 | | -import qualified Test.Tasty.Hedgehog as Tasty |
| 17 | +import Test.Tasty.Hedgehog (testProperty) |
| 18 | + |
| 19 | +prop :: HasCallStack => String -> (Source -> PropertyT IO ()) -> Tasty.TestTree |
| 20 | +prop desc f |
| 21 | + = testProperty desc |
| 22 | + . property |
| 23 | + $ forAll (Gen.source (Hedgehog.Range.linear 0 100)) |
| 24 | + >>= f |
24 | 25 |
|
25 | 26 | testTree :: Tasty.TestTree |
26 | | -testTree = Tasty.testGroup "Data.Source.spanToRange" |
27 | | - [ Tasty.testProperty "computes single-line ranges" prop_computes_single_line_ranges |
28 | | - ] |
| 27 | +testTree = Tasty.testGroup "Data.Source" |
| 28 | + [ Tasty.testGroup "sourceLineRanges" |
| 29 | + [ prop "produces 1 more range than there are newlines" $ |
| 30 | + \ source -> length (sourceLineRanges source) === succ (Text.count "\n" (toText source)) |
| 31 | + |
| 32 | + , prop "produces exhaustive ranges" $ |
| 33 | + \ source -> foldMap (`slice` source) (sourceLineRanges source) === source |
| 34 | + ] |
| 35 | + |
| 36 | + , Tasty.testGroup "spanToRange" |
| 37 | + [ prop "computes single-line ranges" $ \ source -> do |
| 38 | + let ranges = sourceLineRanges source |
| 39 | + let spans = zipWith (\ i Range {..} -> Span (Pos i 1) (Pos i (succ (end - start)))) [1..] ranges |
| 40 | + fmap (spanToRange source) spans === ranges |
| 41 | + |
| 42 | + , prop "computes multi-line ranges" $ |
| 43 | + \ source -> |
| 44 | + spanToRange source (totalSpan source) === totalRange source |
| 45 | + |
| 46 | + , prop "computes sub-line ranges" $ |
| 47 | + \ s -> let source = "*" <> s <> "*" in |
| 48 | + spanToRange source (insetSpan (totalSpan source)) === insetRange (totalRange source) |
| 49 | + |
| 50 | + , testProperty "inverse of rangeToSpan" . property $ do |
| 51 | + a <- forAll . Gen.source $ Hedgehog.Range.linear 0 100 |
| 52 | + b <- forAll . Gen.source $ Hedgehog.Range.linear 0 100 |
| 53 | + let s = a <> "\n" <> b in spanToRange s (totalSpan s) === totalRange s |
| 54 | + ] |
| 55 | + |
| 56 | + , testProperty "rangeToSpan inverse of spanToRange" . property $ do |
| 57 | + a <- forAll . Gen.source $ Hedgehog.Range.linear 0 100 |
| 58 | + b <- forAll . Gen.source $ Hedgehog.Range.linear 0 100 |
| 59 | + let s = a <> "\n" <> b in rangeToSpan s (totalRange s) === totalSpan s |
| 60 | + |
| 61 | + , Tasty.testGroup "totalSpan" |
| 62 | + [ testProperty "covers single lines" . property $ do |
| 63 | + n <- forAll $ Gen.int (Hedgehog.Range.linear 0 100) |
| 64 | + totalSpan (fromText (Text.replicate n "*")) === Span (Pos 1 1) (Pos 1 (max 1 (succ n))) |
| 65 | + |
| 66 | + , testProperty "covers multiple lines" . property $ do |
| 67 | + n <- forAll $ Gen.int (Hedgehog.Range.linear 0 100) |
| 68 | + totalSpan (fromText (Text.intersperse '\n' (Text.replicate n "*"))) === Span (Pos 1 1) (Pos (max 1 n) (if n > 0 then 2 else 1)) |
| 69 | + ] |
29 | 70 |
|
30 | | -prop_computes_single_line_ranges = Hedgehog.property $ do |
31 | | - source <- Hedgehog.forAll . Gen.source $ Hedgehog.Range.linear 0 100 |
32 | | - let ranges = sourceLineRanges source |
33 | | - spanFromRangeWithIndex i Range{start, end} = Span (Pos i 1) (Pos i (end - start + 1)) |
34 | | - spans = zipWith spanFromRangeWithIndex [1..] ranges |
35 | | - fmap (spanToRange source) spans === ranges |
| 71 | + ] |
36 | 72 |
|
37 | 73 | spec :: Spec |
38 | 74 | spec = parallel $ do |
39 | | - describe "sourceLineRanges" $ pure () |
40 | | - -- prop "produces 1 more range than there are newlines" $ |
41 | | - -- \ source -> length (sourceLineRanges source) `shouldBe` succ (Text.count "\n" (toText source)) |
42 | | - |
43 | | - -- prop "produces exhaustive ranges" $ |
44 | | - -- \ source -> foldMap (`slice` source) (sourceLineRanges source) `shouldBe` source |
45 | | - |
46 | | - -- describe "spanToRange" $ do |
47 | | - |
48 | | - -- prop "computes multi-line ranges" $ |
49 | | - -- \ source -> |
50 | | - -- spanToRange source (totalSpan source) `shouldBe` totalRange source |
51 | | - |
52 | | - -- prop "computes sub-line ranges" $ |
53 | | - -- \ s -> let source = "*" <> s <> "*" in |
54 | | - -- spanToRange source (insetSpan (totalSpan source)) `shouldBe` insetRange (totalRange source) |
55 | | - |
56 | | - -- prop "inverse of rangeToSpan" $ |
57 | | - -- \ a b -> let s = a <> "\n" <> b in spanToRange s (totalSpan s) `shouldBe` totalRange s |
58 | | - |
59 | | - -- describe "rangeToSpan" $ do |
60 | | - -- prop "inverse of spanToRange" $ |
61 | | - -- \ a b -> let s = a <> "\n" <> b in rangeToSpan s (totalRange s) `shouldBe` totalSpan s |
62 | | - |
63 | | - -- describe "totalSpan" $ do |
64 | | - -- prop "covers single lines" $ |
65 | | - -- \ n -> totalSpan (fromText (Text.replicate n "*")) `shouldBe` Span (Pos 1 1) (Pos 1 (max 1 (succ n))) |
66 | | - |
67 | | - -- prop "covers multiple lines" $ |
68 | | - -- \ n -> totalSpan (fromText (Text.intersperse '\n' (Text.replicate n "*"))) `shouldBe` Span (Pos 1 1) (Pos (max 1 n) (if n > 0 then 2 else 1)) |
69 | | - |
70 | | - -- describe "newlineIndices" $ do |
71 | | - -- it "finds \\n" $ |
72 | | - -- let source = "a\nb" in |
73 | | - -- newlineIndices source `shouldBe` [1] |
74 | | - -- it "finds \\r" $ |
75 | | - -- let source = "a\rb" in |
76 | | - -- newlineIndices source `shouldBe` [1] |
77 | | - -- it "finds \\r\\n" $ |
78 | | - -- let source = "a\r\nb" in |
79 | | - -- newlineIndices source `shouldBe` [2] |
80 | | - -- it "finds intermixed line endings" $ |
81 | | - -- let source = "hi\r}\r}\n xxx \r a" in |
82 | | - -- newlineIndices source `shouldBe` [2, 4, 6, 12] |
83 | | - |
84 | | - -- prop "preserves characters" . forAll (toTiers (list +| [chr 0xa0..chr 0x24f])) $ |
85 | | - -- \ c -> Text.unpack (toText (fromText (Text.singleton c))) `shouldBe` [c] |
86 | | - |
87 | | - -- prop "preserves strings" $ |
88 | | - -- \ s -> fromText (toText s) `shouldBe` s |
89 | | - |
| 75 | + describe "newlineIndices" $ do |
| 76 | + it "finds \\n" $ |
| 77 | + let source = "a\nb" in |
| 78 | + newlineIndices source `shouldBe` [1] |
| 79 | + it "finds \\r" $ |
| 80 | + let source = "a\rb" in |
| 81 | + newlineIndices source `shouldBe` [1] |
| 82 | + it "finds \\r\\n" $ |
| 83 | + let source = "a\r\nb" in |
| 84 | + newlineIndices source `shouldBe` [2] |
| 85 | + it "finds intermixed line endings" $ |
| 86 | + let source = "hi\r}\r}\n xxx \r a" in |
| 87 | + newlineIndices source `shouldBe` [2, 4, 6, 12] |
90 | 88 |
|
91 | 89 | insetSpan :: Span -> Span |
92 | 90 | insetSpan sourceSpan = sourceSpan { spanStart = (spanStart sourceSpan) { posColumn = succ (posColumn (spanStart sourceSpan)) } |
|
0 commit comments