trackler 2.0.8.17 → 2.0.8.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/common/exercises/forth/canonical-data.json +307 -321
  3. data/common/exercises/largest-series-product/canonical-data.json +139 -122
  4. data/common/exercises/list-ops/canonical-data.json +162 -141
  5. data/common/exercises/markdown/canonical-data.json +15 -14
  6. data/common/exercises/pov/canonical-data.json +264 -116
  7. data/common/exercises/prime-factors/canonical-data.json +51 -40
  8. data/common/exercises/rail-fence-cipher/canonical-data.json +56 -44
  9. data/common/exercises/react/canonical-data.json +18 -4
  10. data/common/exercises/rectangles/canonical-data.json +16 -1
  11. data/common/exercises/rotational-cipher/canonical-data.json +81 -67
  12. data/common/exercises/run-length-encoding/canonical-data.json +89 -71
  13. data/common/exercises/space-age/canonical-data.json +61 -45
  14. data/common/exercises/sublist/canonical-data.json +136 -99
  15. data/common/exercises/transpose/canonical-data.json +207 -194
  16. data/common/exercises/variable-length-quantity/canonical-data.json +171 -141
  17. data/lib/trackler/version.rb +1 -1
  18. data/tracks/csharp/exercises/diamond/HINTS.md +9 -0
  19. data/tracks/go/README.md +3 -0
  20. data/tracks/go/exercises/queen-attack/queen_attack_test.go +1 -1
  21. data/tracks/haskell/exercises/hamming/src/Hamming.hs +2 -1
  22. data/tracks/java/exercises/flatten-array/src/test/java/FlattenerTest.java +5 -0
  23. data/tracks/ocaml/exercises/anagram/test.ml +2 -2
  24. data/tracks/ocaml/tools/test-generator/src/controller.ml +7 -2
  25. data/tracks/ocaml/tools/test-generator/src/parser.ml +31 -18
  26. data/tracks/ocaml/tools/test-generator/src/template.ml +10 -8
  27. data/tracks/ocaml/tools/test-generator/src/utils.ml +11 -0
  28. data/tracks/ocaml/tools/test-generator/templates/phone-number/template.ml +4 -5
  29. data/tracks/ocaml/tools/test-generator/test/beer-song.json +77 -0
  30. data/tracks/ocaml/tools/test-generator/test/difference_of_squares.json +76 -62
  31. data/tracks/ocaml/tools/test-generator/test/parser_test.ml +11 -9
  32. data/tracks/ocaml/tools/test-generator/test/template_test.ml +2 -2
  33. metadata +4 -2
@@ -1,147 +1,177 @@
1
1
  {
2
- "#": [
2
+ "exercise": "variable-length-quantity",
3
+ "version": "1.0.0",
4
+ "comments": [
3
5
  "JSON doesn't allow hexadecimal literals.",
4
6
  "All numbers are given as decimal literals instead.",
5
7
  "It is highly recommended that your track's test generator display all numbers as hexadecimal literals."
6
8
  ],
7
- "encode": {
8
- "description": ["Encode a series of integers, producing a series of bytes."],
9
- "cases": [
10
- {
11
- "description": "zero",
12
- "input": [0],
13
- "expected": [0]
14
- },
15
- {
16
- "description": "arbitrary single byte",
17
- "input": [64],
18
- "expected": [64]
19
- },
20
- {
21
- "description": "largest single byte",
22
- "input": [127],
23
- "expected": [127]
24
- },
25
- {
26
- "description": "smallest double byte",
27
- "input": [128],
28
- "expected": [129, 0]
29
- },
30
- {
31
- "description": "arbitrary double byte",
32
- "input": [8192],
33
- "expected": [192, 0]
34
- },
35
- {
36
- "description": "largest double byte",
37
- "input": [16383],
38
- "expected": [255, 127]
39
- },
40
- {
41
- "description": "smallest triple byte",
42
- "input": [16384],
43
- "expected": [129, 128, 0]
44
- },
45
- {
46
- "description": "arbitrary triple byte",
47
- "input": [1048576],
48
- "expected": [192, 128, 0]
49
- },
50
- {
51
- "description": "largest triple byte",
52
- "input": [2097151],
53
- "expected": [255, 255, 127]
54
- },
55
- {
56
- "description": "smallest quadruple byte",
57
- "input": [2097152],
58
- "expected": [129, 128, 128, 0]
59
- },
60
- {
61
- "description": "arbitrary quadruple byte",
62
- "input": [134217728],
63
- "expected": [192, 128, 128, 0]
64
- },
65
- {
66
- "description": "largest quadruple byte",
67
- "input": [268435455],
68
- "expected": [255, 255, 255, 127]
69
- },
70
- {
71
- "description": "smallest quintuple byte",
72
- "input": [268435456],
73
- "expected": [129, 128, 128, 128, 0]
74
- },
75
- {
76
- "description": "arbitrary quintuple byte",
77
- "input": [4278190080],
78
- "expected": [143, 248, 128, 128, 0]
79
- },
80
- {
81
- "description": "maximum 32-bit integer input",
82
- "input": [4294967295],
83
- "expected": [143, 255, 255, 255, 127]
84
- },
85
- {
86
- "description": "two single-byte values",
87
- "input": [64, 127],
88
- "expected": [64, 127]
89
- },
90
- {
91
- "description": "two multi-byte values",
92
- "input": [16384, 1193046],
93
- "expected": [129, 128, 0, 200, 232, 86]
94
- },
95
- {
96
- "description": "many multi-byte values",
97
- "input": [8192, 1193046, 268435455, 0, 16383, 16384],
98
- "expected": [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0]
99
- }
100
- ]
101
- },
102
- "decode": {
103
- "description": ["Decode a series of bytes, producing a series of integers."],
104
- "cases": [
105
- {
106
- "description": "one byte",
107
- "input": [127],
108
- "expected": [127]
109
- },
110
- {
111
- "description": "two bytes",
112
- "input": [192, 0],
113
- "expected": [8192]
114
- },
115
- {
116
- "description": "three bytes",
117
- "input": [255, 255, 127],
118
- "expected": [2097151]
119
- },
120
- {
121
- "description": "four bytes",
122
- "input": [129, 128, 128, 0],
123
- "expected": [2097152]
124
- },
125
- {
126
- "description": "maximum 32-bit integer",
127
- "input": [143, 255, 255, 255, 127],
128
- "expected": [4294967295]
129
- },
130
- {
131
- "description": "incomplete sequence causes error",
132
- "input": [255],
133
- "expected": null
134
- },
135
- {
136
- "description": "incomplete sequence causes error, even if value is zero",
137
- "input": [128],
138
- "expected": null
139
- },
140
- {
141
- "description": "multiple values",
142
- "input": [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0],
143
- "expected": [8192, 1193046, 268435455, 0, 16383, 16384]
144
- }
145
- ]
146
- }
9
+ "cases": [
10
+ {
11
+ "description": "Encode a series of integers, producing a series of bytes.",
12
+ "cases": [
13
+ {
14
+ "description": "zero",
15
+ "property": "encode",
16
+ "input": [0],
17
+ "expected": [0]
18
+ },
19
+ {
20
+ "description": "arbitrary single byte",
21
+ "property": "encode",
22
+ "input": [64],
23
+ "expected": [64]
24
+ },
25
+ {
26
+ "description": "largest single byte",
27
+ "property": "encode",
28
+ "input": [127],
29
+ "expected": [127]
30
+ },
31
+ {
32
+ "description": "smallest double byte",
33
+ "property": "encode",
34
+ "input": [128],
35
+ "expected": [129,0]
36
+ },
37
+ {
38
+ "description": "arbitrary double byte",
39
+ "property": "encode",
40
+ "input": [8192],
41
+ "expected": [192, 0]
42
+ },
43
+ {
44
+ "description": "largest double byte",
45
+ "property": "encode",
46
+ "input": [16383],
47
+ "expected": [255, 127]
48
+ },
49
+ {
50
+ "description": "smallest triple byte",
51
+ "property": "encode",
52
+ "input": [16384],
53
+ "expected": [129, 128, 0]
54
+ },
55
+ {
56
+ "description": "arbitrary triple byte",
57
+ "property": "encode",
58
+ "input": [1048576],
59
+ "expected": [192, 128, 0]
60
+ },
61
+ {
62
+ "description": "largest triple byte",
63
+ "property": "encode",
64
+ "input": [2097151],
65
+ "expected": [255, 255, 127]
66
+ },
67
+ {
68
+ "description": "smallest quadruple byte",
69
+ "property": "encode",
70
+ "input": [2097152],
71
+ "expected": [129, 128, 128, 0]
72
+ },
73
+ {
74
+ "description": "arbitrary quadruple byte",
75
+ "property": "encode",
76
+ "input": [134217728],
77
+ "expected": [192, 128, 128, 0]
78
+ },
79
+ {
80
+ "description": "largest quadruple byte",
81
+ "property": "encode",
82
+ "input": [268435455],
83
+ "expected": [255, 255, 255, 127]
84
+ },
85
+ {
86
+ "description": "smallest quintuple byte",
87
+ "property": "encode",
88
+ "input": [268435456],
89
+ "expected": [129, 128, 128, 128, 0]
90
+ },
91
+ {
92
+ "description": "arbitrary quintuple byte",
93
+ "property": "encode",
94
+ "input": [4278190080],
95
+ "expected": [143, 248, 128, 128, 0]
96
+ },
97
+ {
98
+ "description": "maximum 32-bit integer input",
99
+ "property": "encode",
100
+ "input": [4294967295],
101
+ "expected": [143, 255, 255, 255, 127]
102
+ },
103
+ {
104
+ "description": "two single-byte values",
105
+ "property": "encode",
106
+ "input": [64, 127],
107
+ "expected": [64, 127]
108
+ },
109
+ {
110
+ "description": "two multi-byte values",
111
+ "property": "encode",
112
+ "input": [16384, 1193046],
113
+ "expected": [129, 128, 0, 200, 232, 86]
114
+ },
115
+ {
116
+ "description": "many multi-byte values",
117
+ "property": "encode",
118
+ "input": [8192, 1193046, 268435455, 0, 16383, 16384],
119
+ "expected": [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0]
120
+ }
121
+ ]
122
+ },
123
+ {
124
+ "description": "Decode a series of bytes, producing a series of integers.",
125
+ "cases": [
126
+ {
127
+ "description": "one byte",
128
+ "property": "decode",
129
+ "input": [127],
130
+ "expected": [127]
131
+ },
132
+ {
133
+ "description": "two bytes",
134
+ "property": "decode",
135
+ "input": [192, 0],
136
+ "expected": [8192]
137
+ },
138
+ {
139
+ "description": "three bytes",
140
+ "property": "decode",
141
+ "input": [255, 255, 127],
142
+ "expected": [2097151]
143
+ },
144
+ {
145
+ "description": "four bytes",
146
+ "property": "decode",
147
+ "input": [129, 128, 128, 0],
148
+ "expected": [2097152]
149
+ },
150
+ {
151
+ "description": "maximum 32-bit integer",
152
+ "property": "decode",
153
+ "input": [143, 255, 255, 255, 127],
154
+ "expected": [4294967295]
155
+ },
156
+ {
157
+ "description": "incomplete sequence causes error",
158
+ "property": "decode",
159
+ "input": [255],
160
+ "expected": null
161
+ },
162
+ {
163
+ "description": "incomplete sequence causes error, even if value is zero",
164
+ "property": "decode",
165
+ "input": [128],
166
+ "expected": null
167
+ },
168
+ {
169
+ "description": "multiple values",
170
+ "property": "decode",
171
+ "input": [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0],
172
+ "expected": [8192, 1193046, 268435455, 0, 16383, 16384]
173
+ }
174
+ ]
175
+ }
176
+ ]
147
177
  }
@@ -1,3 +1,3 @@
1
1
  module Trackler
2
- VERSION = "2.0.8.17"
2
+ VERSION = "2.0.8.18"
3
3
  end
@@ -0,0 +1,9 @@
1
+ ## Hints
2
+ The tests in this exercise are different from your usual tests. Normally, a test checks if for a given input, the output matches the expected value. This is called *value-based testing*. However, this exercise uses *property-based testing*, where the tests check if for a range of inputs, the output has a specific property. The two key differences that differentiate property-based testing from value-based testing are:
3
+
4
+ 1. A property-based test works not with a single input value, but with many.
5
+ 1. A property-based test verifies properties, not concrete values.
6
+
7
+ For this exercise, the tests all verify a property of the diamond shape your code should be producing. Furthermore, all tests check if the property they test holds for all valid input letters ('A' to 'Z').
8
+
9
+ For more information on property-based testing, see [this article](http://www.erikschierboom.com/2016/02/22/property-based-testing/).
data/tracks/go/README.md CHANGED
@@ -26,6 +26,9 @@ Test your clone by cding to the xgo directory and typing
26
26
  Note that unlike most other Go code, it is not necessary to clone this to your GOPATH.
27
27
  This is because this repo only imports from the standard library and isn't expected to be imported by other packages.
28
28
 
29
+ There is a [misspelling tool](https://github.com/client9/misspell). You can install and occasionally run it to
30
+ find low hanging typo problems. [#570](https://github.com/exercism/xgo/pull/570) It's not added into CI since it could give false positives.
31
+
29
32
  ## Contributing Guide
30
33
 
31
34
  Please be familiar with the [contributing guide](https://github.com/exercism/x-common/blob/master/CONTRIBUTING.md)
@@ -33,7 +33,7 @@ var tests = []struct {
33
33
 
34
34
  func TestTestVersion(t *testing.T) {
35
35
  if testVersion != targetTestVersion {
36
- t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
36
+ t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
37
37
  }
38
38
  }
39
39
 
@@ -1,3 +1,4 @@
1
1
  module Hamming (distance) where
2
2
 
3
- distance = error "You need to implement this function."
3
+ distance :: String -> String -> Maybe Int
4
+ distance xs ys = error "You need to implement this function."
@@ -1,5 +1,6 @@
1
1
  import org.junit.Before;
2
2
  import org.junit.Test;
3
+ import org.junit.Ignore;
3
4
 
4
5
  import static java.util.Arrays.asList;
5
6
  import static java.util.Collections.emptyList;
@@ -32,6 +33,7 @@ public final class FlattenerTest {
32
33
  8)));
33
34
  }
34
35
 
36
+ @Ignore
35
37
  @Test
36
38
  public void testFiveLevelsOfNestingWithNoNulls() {
37
39
  assertEquals(
@@ -54,6 +56,7 @@ public final class FlattenerTest {
54
56
  "-2")));
55
57
  }
56
58
 
59
+ @Ignore
57
60
  @Test
58
61
  public void testSixLevelsOfNestingWithNoNulls() {
59
62
  assertEquals(
@@ -76,6 +79,7 @@ public final class FlattenerTest {
76
79
  "8")));
77
80
  }
78
81
 
82
+ @Ignore
79
83
  @Test
80
84
  public void testSixLevelsOfNestingWithNulls() {
81
85
  assertEquals(
@@ -99,6 +103,7 @@ public final class FlattenerTest {
99
103
  "negative two")));
100
104
  }
101
105
 
106
+ @Ignore
102
107
  @Test
103
108
  public void testNestedListsFullOfNullsOnly() {
104
109
  assertEquals(
@@ -13,13 +13,13 @@ let tests = [
13
13
  ae ["tan"] (anagrams "ant" ["tan"; "stand"; "at"]);
14
14
  "does not detect false positives" >::
15
15
  ae [] (anagrams "galea" ["eagle"]);
16
- "detects multiple anagrams" >::
16
+ "detects two anagrams" >::
17
17
  ae ["stream"; "maters"] (anagrams "master" ["stream"; "pigeon"; "maters"]);
18
18
  "does not detect anagram subsets" >::
19
19
  ae [] (anagrams "good" ["dog"; "goody"]);
20
20
  "detects anagram" >::
21
21
  ae ["inlets"] (anagrams "listen" ["enlists"; "google"; "inlets"; "banana"]);
22
- "detects multiple anagrams" >::
22
+ "detects three anagrams" >::
23
23
  ae ["gallery"; "regally"; "largely"] (anagrams "allergy" ["gallery"; "ballerina"; "regally"; "clergy"; "largely"; "leading"]);
24
24
  "does not detect identical words" >::
25
25
  ae ["cron"] (anagrams "corn" ["corn"; "dark"; "Corn"; "rank"; "CORN"; "cron"; "park"]);
@@ -22,6 +22,12 @@ let find_canonical_data_files = find_nested_files "canonical-data.json"
22
22
  let combine_files (template_files: (string * content) list) (canonical_data_files: (string * content) list): (string * content * content) list =
23
23
  List.filter_map template_files ~f:(fun (n,t) -> (List.Assoc.find canonical_data_files ~equal:String.equal n |> Option.map ~f:(fun c -> (n,t,c))))
24
24
 
25
+ (* pangram in the canonical data is a suite but it does not really need to be as there's only one group. Convert a Suite to
26
+ a Single test in this case, to simplify the template. *)
27
+ let simplify_single_test_suite tests = match tests with
28
+ | Suite [{name = name; cases = cases}] -> Single cases
29
+ | x -> x
30
+
25
31
  let generate_code ~(slug: string) ~(template_file: content) ~(canonical_data_file: content): (content, content) Result.t =
26
32
  let template = find_template template_file in
27
33
  let edit_expected = edit_expected ~stringify:json_to_string ~slug in
@@ -30,7 +36,7 @@ let generate_code ~(slug: string) ~(template_file: content) ~(canonical_data_fil
30
36
  let open Result.Monad_infix in
31
37
  Result.of_option template ("cannot recognize file for " ^ slug ^ " as a template") >>= fun template ->
32
38
  parse_json_text canonical_data_file (expected_key_name slug) (cases_name slug)
33
- |> Result.map_error ~f:show_error >>= (function
39
+ |> Result.map_error ~f:show_error >>| simplify_single_test_suite >>= (function
34
40
  | Single cases ->
35
41
  fill_in_template template.template slug cases
36
42
  |> fill_tests template
@@ -38,7 +44,6 @@ let generate_code ~(slug: string) ~(template_file: content) ~(canonical_data_fil
38
44
  | Suite tests ->
39
45
  List.map tests ~f:(fun {name;cases} -> (name, fill_in_template template.template name cases))
40
46
  |> fill_suite template
41
- |> Result.return
42
47
  )
43
48
 
44
49
  let output_tests (files: (string * content * content) list) (output_folder: string) ~(generated_folder: string): unit =
@@ -24,7 +24,7 @@ let parse_case (expected_key: string) (s: json): (case, error) Result.t = match
24
24
 
25
25
  let parse_cases (text: string) (cases_key: string): (json, error) Result.t =
26
26
  match from_string text |> member cases_key with
27
- | `Null -> Error (TestMustHaveKeyCalledCases "xx")
27
+ | `Null -> Error (TestMustHaveKeyCalledCases cases_key)
28
28
  | json -> Ok json
29
29
 
30
30
  let parse_single (text: string) (expected_key: string) (cases_key: string): (tests, error) Result.t =
@@ -34,15 +34,34 @@ let parse_single (text: string) (expected_key: string) (cases_key: string): (tes
34
34
  (sequence >> (List.map ~f:(parse_case expected_key))) >>= fun ts ->
35
35
  Result.return (Single ts)
36
36
 
37
- let is_suite (json: json) (cases_key: string) =
38
- let ignorable_keys = ["exercise"; "version"; "methods"; "comments"] in
39
- let keys = List.filter (keys json) ~f:(Fn.non (List.mem ignorable_keys)) in
40
- let keys = List.sort keys ~cmp:String.compare in
41
- not (List.is_empty keys || keys = [cases_key] || keys = ["#"; cases_key])
37
+ let rec to_cases case: (case list, error) Result.t =
38
+ let open Result.Monad_infix in
39
+ find_note case "description" NoDescription >>= to_string_note BadDescription >>= fun desc ->
40
+ let cases = List.Assoc.find case "cases" in
41
+ match cases with
42
+ | Some cases -> to_list_note UnrecognizedJson cases >>= fun cases ->
43
+ List.map cases ~f:(to_assoc_note UnrecognizedJson) |> sequence >>= fun x ->
44
+ List.map x ~f:to_cases |> sequence |> Result.map ~f:List.concat
45
+ | None ->
46
+ find_note case "expected" (NoExpected "expected") >>= fun expected ->
47
+ Result.return [{description = desc; parameters = case; expected = expected}]
48
+
49
+ let convert_cases_description_to_name desc =
50
+ String.lowercase desc |> String.substr_replace_all ~pattern:" " ~with_:"_"
42
51
 
43
- let merge_result = function
44
- | (_, Error x) -> Error x
45
- | (n, Ok c) -> Ok {name = n; cases = c}
52
+ let suite_case json: (test, error) Result.t =
53
+ let open Result.Monad_infix in
54
+ to_assoc_note UnrecognizedJson json >>= fun case ->
55
+ find_note case "description" NoDescription >>= to_string_note BadDescription >>= fun desc ->
56
+ find_note case "cases" ExpectingListOfCases >>= to_list_note ExpectingListOfCases >>= fun case_assocs ->
57
+ List.map ~f:(to_assoc_note ExpectingMapForCase) case_assocs |> sequence >>= fun cases ->
58
+ List.map cases ~f:to_cases |> sequence >>= fun cases ->
59
+ Result.return {name = convert_cases_description_to_name desc; cases = List.concat cases}
60
+
61
+ let suite_cases (json: json) (cases_key: string): (test list, error) Result.t =
62
+ let open Result.Monad_infix in
63
+ (member cases_key json |> to_list_note ExpectingListOfCases) >>= fun assoc_cases ->
64
+ List.map ~f:suite_case assoc_cases |> sequence
46
65
 
47
66
  let parse_cases_from_suite name suite expected_key cases_key =
48
67
  let open Result.Monad_infix in
@@ -53,15 +72,9 @@ let parse_cases_from_suite name suite expected_key cases_key =
53
72
  let parse_json_text (text: string) (expected_key: string) (cases_key: string): (tests, error) Result.t =
54
73
  let open Result.Monad_infix in
55
74
  let json = from_string text in
56
- if is_suite json cases_key
57
- then
58
- to_assoc_note UnrecognizedJson json >>= fun tests ->
59
- let tests = List.filter tests ~f:(fun (n, _) -> n <> "#") in
60
- let tests = List.map tests ~f:(fun (name, suite) -> merge_result (name, parse_cases_from_suite name suite expected_key cases_key)) in
61
- sequence tests >>= fun tests ->
62
- Ok (Suite tests)
63
- else
64
- parse_single text expected_key cases_key
75
+ match suite_cases json cases_key with
76
+ | Ok suite_cases -> Ok (Suite suite_cases)
77
+ | Error _ -> parse_single text expected_key cases_key
65
78
 
66
79
  let show_error = function
67
80
  | TestMustHaveKeyCalledCases name -> "Test named '" ^ name ^ "' is expected to have an object with a key: 'cases'"
@@ -40,21 +40,23 @@ let fill_tests (template: t) (substs: subst list): string =
40
40
  let join = String.concat_array ~sep:"\n" in
41
41
  String.concat [join before; join subst; join after] ~sep:"\n"
42
42
 
43
- let fill_single_suite (template: t) (suite_substs: string * subst list): string =
43
+ let fill_single_suite (template: t) (suite_substs: string * subst list): (string, string) Result.t =
44
+ let open Result.Monad_infix in
44
45
  let (suite_name, substs) = suite_substs in
45
- let suite_name_line = Option.value_exn template.suite_name_line in
46
- let suite_end_line = Option.value_exn template.suite_end in
46
+ Result.of_option template.suite_name_line ~error:"no suite name" >>= fun suite_name_line ->
47
+ Result.of_option template.suite_end ~error:"no suite end" >>= fun suite_end_line ->
47
48
  let lines = String.split_lines template.file_text |> Fn.flip List.drop suite_name_line |> List.to_array in
48
49
  Array.replace lines 0 ~f:(String.substr_replace_all ~pattern:"(* SUITE *)$(suite_name)_tests" ~with_:(suite_name ^ "_tests"));
49
50
  let before = Array.slice lines 0 (template.start - suite_name_line) in
50
51
  let subst = Array.of_list (List.map ~f:subst_to_string substs) in
51
52
  let after = Array.slice lines (template.finish - suite_name_line + 1) (suite_end_line - suite_name_line) in
52
53
  let join = String.concat_array ~sep:"\n" in
53
- String.concat [join before; join subst; join after] ~sep:"\n"
54
+ Result.return @@ (String.concat [join before; join subst; join after] ~sep:"\n") ^ "\n"
54
55
 
55
- let fill_suite (template: t) (suite_substs: (string * subst list) list): string =
56
- let fills = List.map suite_substs ~f:(fun x -> (fill_single_suite template x) ^ "\n") in
57
- let suite_name_line = Option.value_exn template.suite_name_line in
56
+ let fill_suite (template: t) (suite_substs: (string * subst list) list): (string, string) Result.t =
57
+ let open Result.Monad_infix in
58
+ List.map suite_substs ~f:(fun x -> (fill_single_suite template x)) |> sequence >>= fun fills ->
59
+ Result.of_option template.suite_name_line "no suite name line" >>= fun suite_name_line ->
58
60
  let lines = String.split_lines template.file_text |> List.to_array in
59
61
  let before = Array.slice lines 0 suite_name_line in
60
62
  let subst = Array.of_list fills in
@@ -62,4 +64,4 @@ let fill_suite (template: t) (suite_substs: (string * subst list) list): string
62
64
  let join = String.concat_array ~sep:"\n" in
63
65
  let generated = String.concat [join before; join subst; join after] ~sep:"\n" in
64
66
  let all_suite_names = String.concat ~sep:"; " @@ List.map ~f:(fun (s,_) -> s ^ "_tests") suite_substs in
65
- String.substr_replace_all generated ~pattern:"(* suite-all-names *)" ~with_:all_suite_names
67
+ Result.return @@ String.substr_replace_all generated ~pattern:"(* suite-all-names *)" ~with_:all_suite_names
@@ -27,15 +27,26 @@ let to_list_note error json =
27
27
  let to_assoc_note error json =
28
28
  try Ok (to_assoc json) with Type_error _ -> Error error
29
29
 
30
+ let to_assoc_option json =
31
+ try Some (to_assoc json) with Type_error _ -> None
32
+
30
33
  let to_string_note error json =
31
34
  try Ok (to_string json) with Type_error _ -> Error error
32
35
 
36
+ let to_string_option json =
37
+ try Some (to_string json) with _ -> None
38
+
33
39
  let safe_to_int_option json =
34
40
  try Some (to_int json) with Type_error _ -> None
35
41
 
36
42
  let member_note error m json =
37
43
  try Ok (member m json) with Type_error _ -> Error error
38
44
 
45
+ let find_note (xs: ('a, 'b) List.Assoc.t) (key: 'a) (error: 'e): ('b, 'e) Result.t =
46
+ match List.Assoc.find xs key with
47
+ | Some v -> Ok v
48
+ | None -> Error error
49
+
39
50
  let (>>) f g = Fn.compose f g
40
51
 
41
52
  let find_arrayi ?start:(start = 0) xs ~f =
@@ -9,13 +9,12 @@ let option_to_string f = function
9
9
  let ae exp got _test_ctxt =
10
10
  assert_equal ~printer:(option_to_string String.to_string) exp got
11
11
 
12
- let (* SUITE *)$(suite_name)_tests = [
12
+ let tests = [
13
13
  (* TEST
14
- "$description" >::
15
- ae $expected (number $phrase);
16
- END TEST *)
14
+ "$description" >::
15
+ ae $expected (number $phrase);
16
+ END TEST *)
17
17
  ]
18
- (* END SUITE *)
19
18
 
20
19
  let () =
21
20
  run_test_tt_main ("phone-number tests" >::: number_tests)