trackler 2.0.3.8 → 2.0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/common/exercises/book-store/description.md +67 -0
- data/common/exercises/book-store/metadata.yml +4 -0
- data/lib/trackler/version.rb +1 -1
- data/tracks/crystal/exercises/difference-of-squares/src/difference_of_squares.cr +1 -0
- data/tracks/ecmascript/exercises/custom-set/package.json +1 -0
- data/tracks/ecmascript/exercises/palindrome-products/palindrome-products.js +18 -18
- data/tracks/fsharp/docs/INSTALLATION.md +2 -3
- data/tracks/fsharp/docs/TESTS.md +1 -1
- data/tracks/fsharp/exercises/accumulate/HINTS.md +3 -0
- data/tracks/fsharp/exercises/diamond/HINTS.md +2 -0
- data/tracks/fsharp/exercises/grains/HINTS.md +1 -1
- data/tracks/fsharp/exercises/hello-world/HINTS.md +1 -1
- data/tracks/fsharp/exercises/parallel-letter-frequency/HINTS.md +3 -0
- data/tracks/fsharp/exercises/poker/HINTS.md +2 -0
- data/tracks/fsharp/exercises/raindrops/HINTS.md +2 -0
- data/tracks/fsharp/exercises/space-age/HINTS.md +3 -0
- data/tracks/javascript/exercises/bowling/bowling.spec.js +83 -63
- data/tracks/ocaml/config.json +5 -0
- data/tracks/ocaml/exercises/leap/test.ml +7 -13
- data/tracks/ocaml/exercises/robot-name/.merlin +3 -0
- data/tracks/ocaml/exercises/robot-name/Makefile +11 -0
- data/tracks/ocaml/exercises/robot-name/example.ml +26 -0
- data/tracks/ocaml/exercises/robot-name/robot_name.mli +7 -0
- data/tracks/ocaml/exercises/robot-name/test.ml +64 -0
- data/tracks/ocaml/tools/test-generator/Makefile +3 -0
- data/tracks/ocaml/tools/test-generator/README.md +1 -0
- data/tracks/ocaml/tools/test-generator/src/canonical_data_checker.ml +23 -0
- data/tracks/ocaml/tools/test-generator/src/controller.ml +12 -0
- data/tracks/ocaml/tools/test-generator/src/parser.ml +29 -22
- data/tracks/ocaml/tools/test-generator/src/utils.ml +8 -0
- data/tracks/ocaml/tools/test-generator/test/clock.json +437 -0
- data/tracks/ocaml/tools/test-generator/test/parser_test.ml +18 -4
- data/tracks/ocaml/tools/test-generator/test/with-methods-key.json +22 -0
- data/tracks/perl6/config.json +5 -0
- data/tracks/perl6/exercises/wordy/Example.p6 +16 -0
- data/tracks/perl6/exercises/wordy/cases.json +89 -0
- data/tracks/perl6/exercises/wordy/wordy.t +29 -0
- data/tracks/ruby/exercises/acronym/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/alphametics/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/anagram/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/binary/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/bowling/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/bracket-push/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/clock/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/connect/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/custom-set/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/difference-of-squares/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/dominoes/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/gigasecond/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/hamming/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/hello-world/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/isogram/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/largest-series-product/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/leap/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/nth-prime/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/pangram/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/queen-attack/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/raindrops/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/rna-transcription/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/roman-numerals/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/run-length-encoding/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/sieve/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/tournament/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/transpose/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/triangle/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/two-bucket/{.version → .meta/.version} +0 -0
- data/tracks/ruby/exercises/word-count/{.version → .meta/.version} +0 -0
- data/tracks/ruby/lib/generator.rb +15 -4
- data/tracks/rust/config.json +1 -0
- data/tracks/rust/exercises/largest-series-product/.gitignore +7 -0
- data/tracks/rust/exercises/largest-series-product/Cargo.toml +3 -0
- data/tracks/rust/exercises/largest-series-product/HINTS.md +6 -0
- data/tracks/rust/exercises/largest-series-product/example.rs +22 -0
- data/tracks/rust/exercises/largest-series-product/tests/largest-series-product.rs +105 -0
- data/tracks/rust/problems.md +1 -0
- data/tracks/scala/.gitignore +1 -0
- data/tracks/scala/exercises/accumulate/src/test/scala/{accumulate_test.scala → AccumulateTest.scala} +0 -0
- data/tracks/scala/exercises/allergies/src/test/scala/{allergies_test.scala → AllergiesTest.scala} +0 -0
- data/tracks/scala/exercises/anagram/src/test/scala/{anagram_test.scala → AnagramTest.scala} +0 -0
- data/tracks/scala/exercises/binary/src/test/scala/{binary_test.scala → BinaryTest.scala} +0 -0
- data/tracks/scala/exercises/bob/src/test/scala/{bob_test.scala → BobTest.scala} +0 -0
- data/tracks/scala/exercises/difference-of-squares/src/test/scala/{squares_test.scala → SquaresTest.scala} +0 -0
- data/tracks/scala/exercises/etl/src/test/scala/{etl_test.scala → EtlTest.scala} +0 -0
- data/tracks/scala/exercises/gigasecond/src/test/scala/{gigasecond_test.scala → GigasecondTest.scala} +0 -0
- data/tracks/scala/exercises/grade-school/src/test/scala/{grade_school_test.scala → GradeSchoolTest.scala} +0 -0
- data/tracks/scala/exercises/grains/src/test/scala/{grains_test.scala → GrainsTest.scala} +0 -0
- data/tracks/scala/exercises/hamming/src/test/scala/{hamming_test.scala → HammingTest.scala} +0 -0
- data/tracks/scala/exercises/hello-world/src/test/scala/HelloWorldTest.scala +2 -0
- data/tracks/scala/exercises/leap/src/test/scala/{leap_test.scala → LeapTest.scala} +0 -0
- data/tracks/scala/exercises/meetup/src/test/scala/{meetup_test.scala → MeetupTest.scala} +0 -0
- data/tracks/scala/exercises/nth-prime/example.scala +3 -1
- data/tracks/scala/exercises/nth-prime/src/test/scala/PrimeTest.scala +11 -6
- data/tracks/scala/exercises/nucleotide-count/src/test/scala/{nucleotide_count_test.scala → NucleotideCountTest.scala} +0 -0
- data/tracks/scala/exercises/palindrome-products/src/test/scala/PalindromeProductsTest.scala +7 -0
- data/tracks/scala/exercises/pangram/src/test/scala/{PangramsTest.scala → PangramTest.scala} +0 -0
- data/tracks/scala/exercises/parallel-letter-frequency/HINTS.md +26 -0
- data/tracks/scala/exercises/phone-number/src/test/scala/{phone_number_test.scala → PhoneNumberTest.scala} +0 -0
- data/tracks/scala/exercises/prime-factors/src/test/scala/{primefactors_test.scala → PrimefactorsTest.scala} +10 -0
- data/tracks/scala/exercises/raindrops/src/test/scala/{raindrops_test.scala → RaindropsTest.scala} +15 -0
- data/tracks/scala/exercises/rna-transcription/src/test/scala/{transcription_test.scala → RnaTranscriptionTest.scala} +8 -0
- data/tracks/scala/exercises/robot-name/src/test/scala/{robot_name_test.scala → RobotNameTest.scala} +0 -0
- data/tracks/scala/exercises/roman-numerals/src/test/scala/{roman_numerals_test.scala → RomanNumeralsTest.scala} +17 -0
- data/tracks/scala/exercises/saddle-points/src/test/scala/{SaddlePointsSpecs.scala → SaddlePointsTest.scala} +3 -0
- data/tracks/scala/exercises/scrabble-score/src/test/scala/{scrabble_score_test.scala → ScrabbleScoreTest.scala} +0 -0
- data/tracks/scala/exercises/sgf-parsing/src/test/scala/SgfTest.scala +1 -0
- data/tracks/scala/exercises/space-age/src/test/scala/{space_age_test.scala → SpaceAgeTest.scala} +0 -0
- data/tracks/scala/exercises/sublist/src/test/scala/{sublist_test.scala → SublistTest.scala} +0 -0
- data/tracks/scala/exercises/triangle/src/test/scala/{triangle_test.scala → TriangleTest.scala} +7 -0
- data/tracks/scala/exercises/word-count/src/test/scala/{word_count_test.scala → WordCountTest.scala} +0 -0
- data/tracks/scala/exercises/zipper/src/test/scala/ZipperTest.scala +7 -0
- data/tracks/swift/config.json +19 -0
- data/tracks/swift/docs/TESTS.md +114 -23
- data/tracks/swift/exercises/beer-song/BeerSongExample.swift +32 -0
- data/tracks/swift/exercises/beer-song/BeerSongTest.swift +44 -0
- data/tracks/swift/exercises/hello-world/{helloWorldExample.swift → HelloWorldExample.swift} +4 -0
- data/tracks/swift/exercises/hello-world/{helloWorldTest/helloWorldTest.swift → helloWorldTest.swift} +1 -1
- data/tracks/swift/exercises/sublist/SubListTest.swift +95 -0
- data/tracks/swift/exercises/sublist/SublistExample.swift +72 -0
- data/tracks/swift/img/page_assets/001-splash.png +0 -0
- data/tracks/swift/img/page_assets/002-templateChooser.png +0 -0
- data/tracks/swift/img/page_assets/003-nameProject.jpg +0 -0
- data/tracks/swift/img/page_assets/004-saveProject.jpg +0 -0
- data/tracks/swift/img/page_assets/005-folderLayout.png +0 -0
- data/tracks/swift/img/page_assets/006-newProjectInitial.jpg +0 -0
- data/tracks/swift/img/page_assets/007-fileInspectorUpdate.png +0 -0
- data/tracks/swift/img/page_assets/008-templateChooserSwift.png +0 -0
- data/tracks/swift/img/page_assets/009-importTestSource.png +0 -0
- data/tracks/swift/img/page_assets/010-testsImportExample.png +0 -0
- data/tracks/swift/img/page_assets/011-finalLayoutExample.png +0 -0
- data/tracks/swift/xcodeProject/xSwift.xcodeproj/project.pbxproj +36 -56
- metadata +103 -65
- data/tracks/swift/exercises/hello-world/helloWorld.swift +0 -1
- data/tracks/swift/exercises/hello-world/helloWorld.xcodeproj/project.pbxproj +0 -256
- data/tracks/swift/exercises/hello-world/helloWorld.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- data/tracks/swift/exercises/hello-world/helloWorldTest/Info.plist +0 -24
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
open Core.Std
|
|
2
|
+
|
|
3
|
+
type robot = {mutable index : int}
|
|
4
|
+
|
|
5
|
+
let index = ref (-1)
|
|
6
|
+
|
|
7
|
+
let unique_ids: int array =
|
|
8
|
+
let ids = Array.init (26*26*1000) ~f:Fn.id in
|
|
9
|
+
Array.permute ids;
|
|
10
|
+
ids
|
|
11
|
+
|
|
12
|
+
let new_robot () =
|
|
13
|
+
index := !index + 1;
|
|
14
|
+
{index = unique_ids.(!index)}
|
|
15
|
+
|
|
16
|
+
let name r =
|
|
17
|
+
let n = r.index in
|
|
18
|
+
let letters_part = n / 1000 in
|
|
19
|
+
let letter_A = Char.to_int 'A' in
|
|
20
|
+
let first_letter = Char.of_int_exn (letter_A + (letters_part / 26)) in
|
|
21
|
+
let second_letter = Char.of_int_exn (letter_A + (letters_part % 26)) in
|
|
22
|
+
sprintf "%c%c%03d" first_letter second_letter (n % 1000)
|
|
23
|
+
|
|
24
|
+
let reset r =
|
|
25
|
+
index := !index + 1;
|
|
26
|
+
r.index <- unique_ids.(!index);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
open Core.Std
|
|
2
|
+
open OUnit2
|
|
3
|
+
open Robot_name
|
|
4
|
+
|
|
5
|
+
let assert_matches_spec name =
|
|
6
|
+
let is_valid_letter ch = 'A' <= ch && ch <= 'Z' in
|
|
7
|
+
let is_valid_digit ch = '0' <= ch && ch <= '9' in
|
|
8
|
+
assert_equal ~printer:Int.to_string 5 (String.length name);
|
|
9
|
+
assert_bool ("First character must be from A to Z") (is_valid_letter @@ name.[0]);
|
|
10
|
+
assert_bool ("Second character must be from A to Z") (is_valid_letter @@ name.[1]);
|
|
11
|
+
assert_bool ("Third character must be from 0 to 9") (is_valid_digit @@ name.[2]);
|
|
12
|
+
assert_bool ("Fourth character must be from 0 to 9") (is_valid_digit @@ name.[3]);
|
|
13
|
+
assert_bool ("Fifth character must be from 0 to 9") (is_valid_digit @@ name.[4]);;
|
|
14
|
+
|
|
15
|
+
let basic_tests = [
|
|
16
|
+
"a robot has a name of 2 letters followed by 3 numbers" >:: (fun _ctxt ->
|
|
17
|
+
let n = name (new_robot ()) in
|
|
18
|
+
assert_matches_spec n
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
"resetting a robot's name gives it a different name" >:: (fun _ctxt ->
|
|
22
|
+
let r = new_robot () in
|
|
23
|
+
let n1 = name r in
|
|
24
|
+
reset r;
|
|
25
|
+
let n2 = name r in
|
|
26
|
+
assert_bool ("'" ^ n1 ^ "' was repeated") (n1 <> n2)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
"after reset the robot's name still matches the specification" >:: (fun _ctxt ->
|
|
30
|
+
let r = new_robot () in
|
|
31
|
+
reset r;
|
|
32
|
+
let n = name r in
|
|
33
|
+
assert_matches_spec n
|
|
34
|
+
);
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
(*
|
|
38
|
+
Optionally: make this test pass.
|
|
39
|
+
|
|
40
|
+
There are 26 * 26 * 10 * 10 * 10 = 676,000 possible Robot names.
|
|
41
|
+
This test generates all possible Robot names, and checks that there are
|
|
42
|
+
no duplicates. It's harder to make pass than the other tests, so it is left
|
|
43
|
+
as optional.
|
|
44
|
+
|
|
45
|
+
To enable it, uncomment the code in the run_test_tt_main
|
|
46
|
+
line at the bottom of this module.
|
|
47
|
+
*)
|
|
48
|
+
let unique_name_tests = [
|
|
49
|
+
"all possible robot names are distinct" >:: (fun _ctxt ->
|
|
50
|
+
let rs = Array.init (26 * 26 * 1000) ~f:(fun _ -> new_robot ()) in
|
|
51
|
+
let (repeated, _) = Array.fold rs ~init:(String.Set.empty, String.Set.empty) ~f:(fun (repeated, seen) r ->
|
|
52
|
+
let n = name r in
|
|
53
|
+
if Set.mem seen n
|
|
54
|
+
then (Set.add repeated n, seen)
|
|
55
|
+
else (repeated, Set.add seen n)
|
|
56
|
+
) in
|
|
57
|
+
let first_few_repeats = Array.slice (Set.to_array repeated) 0 (min 20 (Set.length repeated)) in
|
|
58
|
+
let failure_message = "first few repeats: " ^ (String.concat_array first_few_repeats ~sep:",") in
|
|
59
|
+
assert_bool failure_message (Set.is_empty repeated)
|
|
60
|
+
);
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
let () =
|
|
64
|
+
run_test_tt_main ("robot-name tests" >::: List.concat [basic_tests (* ; unique_name_tests *)])
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
test: test_gen.native
|
|
2
2
|
@./all_tests.native
|
|
3
3
|
|
|
4
|
+
canonical_data_checker.native: all_tests.native src/*.ml interfaces/*.mli test/*.ml
|
|
5
|
+
@ocamlbuild -use-ocamlfind -tag thread -tag short_paths -cflags -strict-sequence -r -pkg core -pkg yojson -pkg ppx_deriving -pkg ppx_deriving.eq -pkg ppx_deriving.show -Is src,interfaces canonical_data_checker.native
|
|
6
|
+
|
|
4
7
|
test_gen.byte: all_tests.native src/*.ml interfaces/*.mli test/*.ml
|
|
5
8
|
@ocamlbuild -use-ocamlfind -tag thread -tag short_paths -cflags -strict-sequence -r -pkg core -pkg yojson -pkg ppx_deriving -pkg ppx_deriving.eq -pkg ppx_deriving.show -Is src,interfaces test_gen.byte
|
|
6
9
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Placeholder
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
open Core.Std
|
|
2
|
+
|
|
3
|
+
let is_directory =
|
|
4
|
+
Command.Spec.Arg_type.create
|
|
5
|
+
(fun n ->
|
|
6
|
+
match Sys.is_directory n with
|
|
7
|
+
| `Yes -> n
|
|
8
|
+
| `No | `Unknown ->
|
|
9
|
+
eprintf "'%s' is not a regular folder.\n%!" n;
|
|
10
|
+
exit 1
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
let command =
|
|
14
|
+
Command.basic
|
|
15
|
+
~summary:"Reports errors in canonical data."
|
|
16
|
+
Command.Spec.(
|
|
17
|
+
empty
|
|
18
|
+
+> flag "-c" (optional_with_default "../../../x-common/exercises" is_directory) ~doc:"string Directory containing canonical data."
|
|
19
|
+
)
|
|
20
|
+
(fun canonical_data_folder () -> Controller.check_canonical_data canonical_data_folder)
|
|
21
|
+
|
|
22
|
+
let () =
|
|
23
|
+
Command.run ~version:"0.1" command
|
|
@@ -53,3 +53,15 @@ let run ~(templates_folder: string) ~(canonical_data_folder: string) ~(output_fo
|
|
|
53
53
|
let canonical_data_files = find_canonical_data_files canonical_data_folder in
|
|
54
54
|
let combined = combine_files template_files canonical_data_files in
|
|
55
55
|
output_tests combined output_folder
|
|
56
|
+
|
|
57
|
+
let check_canonical_data canonical_data_folder =
|
|
58
|
+
let ok_count = ref 0 in
|
|
59
|
+
let canonical_data_files = find_canonical_data_files canonical_data_folder in
|
|
60
|
+
let canonical_data_files = List.sort canonical_data_files ~cmp:(fun (s1, _) (s2, _) -> String.compare s1 s2) in
|
|
61
|
+
let total_count = List.length canonical_data_files in
|
|
62
|
+
List.iter canonical_data_files ~f:(fun (slug, text) ->
|
|
63
|
+
match parse_json_text text with
|
|
64
|
+
| Error e -> print_endline @@ slug ^ ": " ^ (show_error e)
|
|
65
|
+
| _ -> ok_count := !ok_count + 1
|
|
66
|
+
);
|
|
67
|
+
print_endline @@ "There are " ^ (Int.to_string total_count) ^ " exercises with canonical data, " ^ (Int.to_string !ok_count) ^ " can be parsed."
|
|
@@ -5,19 +5,22 @@ open Yojson.Safe.Util
|
|
|
5
5
|
open Model
|
|
6
6
|
|
|
7
7
|
type error =
|
|
8
|
-
TestMustHaveKeyCalledCases | ExpectingListOfCases | ExpectingMapForCase |
|
|
9
|
-
BadDescription | BadExpected | UnrecognizedJson [@@deriving eq, show]
|
|
8
|
+
TestMustHaveKeyCalledCases of string | ExpectingListOfCases | ExpectingMapForCase |
|
|
9
|
+
NoDescription | BadDescription | NoExpected of string | BadExpected | UnrecognizedJson [@@deriving eq, show]
|
|
10
10
|
|
|
11
|
-
let
|
|
12
|
-
| `Int x -> x
|
|
13
|
-
| _ ->
|
|
11
|
+
let to_int_safe = function
|
|
12
|
+
| `Int x -> Some x
|
|
13
|
+
| _ -> None
|
|
14
14
|
|
|
15
|
-
let to_list_safe xs = match xs with
|
|
15
|
+
let to_list_safe xs = let open Option.Monad_infix in match xs with
|
|
16
16
|
| [] -> Some (StringList [])
|
|
17
17
|
| `String x :: _ -> Some (StringList (List.map xs ~f:to_string))
|
|
18
|
-
| `Int x :: _ ->
|
|
18
|
+
| `Int x :: _ -> List.map xs ~f:to_int_safe |> sequence_option >>= (fun xs -> Some (IntList xs))
|
|
19
19
|
| _ -> None
|
|
20
20
|
|
|
21
|
+
let q xs = let open Option.Monad_infix in
|
|
22
|
+
List.map xs ~f:(fun (k,v) -> (to_int_safe v |> Option.map ~f:(fun v -> (k,v))))
|
|
23
|
+
|
|
21
24
|
let to_parameter (s: json) = match s with
|
|
22
25
|
| `Null -> Some (Null)
|
|
23
26
|
| `String x -> Some (String x)
|
|
@@ -25,7 +28,9 @@ let to_parameter (s: json) = match s with
|
|
|
25
28
|
| `Int x -> Some (Int x)
|
|
26
29
|
| `Bool x -> Some (Bool x)
|
|
27
30
|
| `List x -> to_list_safe x
|
|
28
|
-
| `Assoc
|
|
31
|
+
| `Assoc xs -> let open Option.Monad_infix in
|
|
32
|
+
let xs = List.map xs ~f:(fun (k,v) -> (to_int_safe v |> Option.map ~f:(fun v -> (k,v)))) in
|
|
33
|
+
sequence_option xs >>= fun xs -> Some (IntStringMap xs)
|
|
29
34
|
| _ -> None
|
|
30
35
|
|
|
31
36
|
let parse_parameters (parameters: (string * json) list): parameter elements =
|
|
@@ -36,9 +41,9 @@ let parse_case_assoc (parameters: (string * json) list): (case, error) Result.t
|
|
|
36
41
|
let test_parameters = List.Assoc.remove parameters "description" in
|
|
37
42
|
let test_parameters = List.Assoc.remove test_parameters "expected" in
|
|
38
43
|
let open Result.Monad_infix in
|
|
39
|
-
find "description"
|
|
44
|
+
find "description" NoDescription >>=
|
|
40
45
|
to_string_note BadDescription >>= fun description ->
|
|
41
|
-
find "expected"
|
|
46
|
+
find "expected" (NoExpected description) >>= fun expectedJson ->
|
|
42
47
|
to_parameter expectedJson |> Result.of_option ~error:BadExpected >>= fun expected ->
|
|
43
48
|
Ok {description = description; parameters = parse_parameters test_parameters; expected = expected}
|
|
44
49
|
|
|
@@ -48,7 +53,7 @@ let parse_case (s: json): (case, error) Result.t = match s with
|
|
|
48
53
|
|
|
49
54
|
let parse_cases (text: string): (json, error) Result.t =
|
|
50
55
|
match from_string text |> member "cases" with
|
|
51
|
-
| `Null -> Error TestMustHaveKeyCalledCases
|
|
56
|
+
| `Null -> Error (TestMustHaveKeyCalledCases "xx")
|
|
52
57
|
| json -> Ok json
|
|
53
58
|
|
|
54
59
|
let parse_single (text: string): (tests, error) Result.t =
|
|
@@ -59,17 +64,18 @@ let parse_single (text: string): (tests, error) Result.t =
|
|
|
59
64
|
Result.return (Single ts)
|
|
60
65
|
|
|
61
66
|
let is_suite (json: json) =
|
|
62
|
-
let keys = List.
|
|
67
|
+
let keys = List.filter (keys json) ~f:(fun k -> k <> "methods") in
|
|
68
|
+
let keys = List.sort keys ~cmp:String.compare in
|
|
63
69
|
not (List.is_empty keys || keys = ["cases"] || keys = ["#"; "cases"])
|
|
64
70
|
|
|
65
71
|
let merge_result = function
|
|
66
72
|
| (_, Error x) -> Error x
|
|
67
73
|
| (n, Ok c) -> Ok {name = n; cases = c}
|
|
68
74
|
|
|
69
|
-
let parse_cases_from_suite suite =
|
|
75
|
+
let parse_cases_from_suite name suite =
|
|
70
76
|
let open Result.Monad_infix in
|
|
71
|
-
member_note
|
|
72
|
-
to_list_note
|
|
77
|
+
member_note (TestMustHaveKeyCalledCases name) "cases" suite >>=
|
|
78
|
+
to_list_note ExpectingListOfCases >>= fun tests ->
|
|
73
79
|
List.map tests ~f:parse_case |> sequence
|
|
74
80
|
|
|
75
81
|
let parse_json_text (text: string): (tests, error) Result.t =
|
|
@@ -78,18 +84,19 @@ let parse_json_text (text: string): (tests, error) Result.t =
|
|
|
78
84
|
if is_suite json
|
|
79
85
|
then
|
|
80
86
|
to_assoc_note UnrecognizedJson json >>= fun tests ->
|
|
81
|
-
|
|
87
|
+
let tests = List.filter tests ~f:(fun (n, _) -> n <> "#") in
|
|
88
|
+
let tests = List.map tests ~f:(fun (name, suite) -> merge_result (name, parse_cases_from_suite name suite)) in
|
|
82
89
|
sequence tests >>= fun tests ->
|
|
83
90
|
Ok (Suite tests)
|
|
84
91
|
else
|
|
85
92
|
parse_single text
|
|
86
93
|
|
|
87
94
|
let show_error = function
|
|
88
|
-
| TestMustHaveKeyCalledCases -> "
|
|
89
|
-
"expecting an object with a key: 'cases'"
|
|
95
|
+
| TestMustHaveKeyCalledCases name -> "Test named '" ^ name ^ "' is expected to have an object with a key: 'cases'"
|
|
90
96
|
| ExpectingMapForCase -> "Expected a json map for a test case"
|
|
91
|
-
| ExpectingListOfCases -> "Expected a top level map with key cases, "
|
|
92
|
-
|
|
93
|
-
| BadDescription -> "
|
|
94
|
-
|
|
|
97
|
+
| ExpectingListOfCases -> "Expected a top level map with key cases, and a list of cases as its value."
|
|
98
|
+
| NoDescription -> "Case is missing a description."
|
|
99
|
+
| BadDescription -> "Description is not a string."
|
|
100
|
+
| NoExpected s -> "Case '" ^ s ^ "' is missing an expected key."
|
|
101
|
+
| BadExpected -> "Do not understand type of Expected key."
|
|
95
102
|
| UnrecognizedJson -> "Cannot understand this json."
|
|
@@ -7,9 +7,17 @@ let map2 (f: 'a -> 'b -> 'c) (r1: ('a, 'e) Result.t) (r2: ('b, 'e) Result.t): ('
|
|
|
7
7
|
| (_, Error x) -> Error x
|
|
8
8
|
| (Ok a, Ok b) -> Ok (f a b)
|
|
9
9
|
|
|
10
|
+
let map2_option (f: 'a -> 'b -> 'c) (r1: 'a option) (r2: 'b option): 'c option = match (r1, r2) with
|
|
11
|
+
| (None, _) -> None
|
|
12
|
+
| (_, None) -> None
|
|
13
|
+
| (Some a, Some b) -> Some (f a b)
|
|
14
|
+
|
|
10
15
|
let sequence (rs: (('a, 'e) Result.t) list): (('a list), 'e) Result.t =
|
|
11
16
|
List.fold_right rs ~init:(Ok []) ~f:(map2 (fun x xs -> x :: xs))
|
|
12
17
|
|
|
18
|
+
let sequence_option (rs: ('a option) list): ('a list) option =
|
|
19
|
+
List.fold_right rs ~init:(Some []) ~f:(map2_option (fun x xs -> x :: xs))
|
|
20
|
+
|
|
13
21
|
let to_list_option json =
|
|
14
22
|
try Some (to_list json) with Type_error _ -> None
|
|
15
23
|
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
|
|
2
|
+
{
|
|
3
|
+
"#": [
|
|
4
|
+
"Most languages require constructing a clock with initial values,",
|
|
5
|
+
"adding a positive or negative number of minutes, and testing equality",
|
|
6
|
+
"in some language-native way. Some languages require separate add and",
|
|
7
|
+
"subtract functions. Negative and out of range values are generally",
|
|
8
|
+
"expected to wrap around rather than represent errors."
|
|
9
|
+
],
|
|
10
|
+
"create": {
|
|
11
|
+
"description": [
|
|
12
|
+
"Test creating a new clock with an initial time."
|
|
13
|
+
],
|
|
14
|
+
"cases": [
|
|
15
|
+
{
|
|
16
|
+
"description": "on the hour",
|
|
17
|
+
"hour": 8,
|
|
18
|
+
"minute": 0,
|
|
19
|
+
"expected": "08:00"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"description": "past the hour",
|
|
23
|
+
"hour": 11,
|
|
24
|
+
"minute": 9,
|
|
25
|
+
"expected": "11:09"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"description": "midnight is zero hours",
|
|
29
|
+
"hour": 24,
|
|
30
|
+
"minute": 0,
|
|
31
|
+
"expected": "00:00"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"description": "hour rolls over",
|
|
35
|
+
"hour": 25,
|
|
36
|
+
"minute": 0,
|
|
37
|
+
"expected": "01:00"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"description": "hour rolls over continuously",
|
|
41
|
+
"hour": 100,
|
|
42
|
+
"minute": 0,
|
|
43
|
+
"expected": "04:00"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"description": "sixty minutes is next hour",
|
|
47
|
+
"hour": 1,
|
|
48
|
+
"minute": 60,
|
|
49
|
+
"expected": "02:00"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"description": "minutes roll over",
|
|
53
|
+
"hour": 0,
|
|
54
|
+
"minute": 160,
|
|
55
|
+
"expected": "02:40"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"description": "minutes roll over continuously",
|
|
59
|
+
"hour": 0,
|
|
60
|
+
"minute": 1723,
|
|
61
|
+
"expected": "04:43"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"description": "hour and minutes roll over",
|
|
65
|
+
"hour": 25,
|
|
66
|
+
"minute": 160,
|
|
67
|
+
"expected": "03:40"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"description": "hour and minutes roll over continuously",
|
|
71
|
+
"hour": 201,
|
|
72
|
+
"minute": 3001,
|
|
73
|
+
"expected": "11:01"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"description": "hour and minutes roll over to exactly midnight",
|
|
77
|
+
"hour": 72,
|
|
78
|
+
"minute": 8640,
|
|
79
|
+
"expected": "00:00"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"description": "negative hour",
|
|
83
|
+
"hour": -1,
|
|
84
|
+
"minute": 15,
|
|
85
|
+
"expected": "23:15"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"description": "negative hour rolls over",
|
|
89
|
+
"hour": -25,
|
|
90
|
+
"minute": 0,
|
|
91
|
+
"expected": "23:00"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"description": "negative hour rolls over continuously",
|
|
95
|
+
"hour": -91,
|
|
96
|
+
"minute": 0,
|
|
97
|
+
"expected": "05:00"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"description": "negative minutes",
|
|
101
|
+
"hour": 1,
|
|
102
|
+
"minute": -40,
|
|
103
|
+
"expected": "00:20"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"description": "negative minutes roll over",
|
|
107
|
+
"hour": 1,
|
|
108
|
+
"minute": -160,
|
|
109
|
+
"expected": "22:20"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"description": "negative minutes roll over continuously",
|
|
113
|
+
"hour": 1,
|
|
114
|
+
"minute": -4820,
|
|
115
|
+
"expected": "16:40"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"description": "negative hour and minutes both roll over",
|
|
119
|
+
"hour": -25,
|
|
120
|
+
"minute": -160,
|
|
121
|
+
"expected": "20:20"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"description": "negative hour and minutes both roll over continuously",
|
|
125
|
+
"hour": -121,
|
|
126
|
+
"minute": -5810,
|
|
127
|
+
"expected": "22:10"
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"add": {
|
|
132
|
+
"description": [
|
|
133
|
+
"Test adding and subtracting minutes."
|
|
134
|
+
],
|
|
135
|
+
"cases": [
|
|
136
|
+
{
|
|
137
|
+
"description": "add minutes",
|
|
138
|
+
"hour": 10,
|
|
139
|
+
"minute": 0,
|
|
140
|
+
"add": 3,
|
|
141
|
+
"expected": "10:03"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"description": "add no minutes",
|
|
145
|
+
"hour": 6,
|
|
146
|
+
"minute": 41,
|
|
147
|
+
"add": 0,
|
|
148
|
+
"expected": "06:41"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"description": "add to next hour",
|
|
152
|
+
"hour": 0,
|
|
153
|
+
"minute": 45,
|
|
154
|
+
"add": 40,
|
|
155
|
+
"expected": "01:25"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"description": "add more than one hour",
|
|
159
|
+
"hour": 10,
|
|
160
|
+
"minute": 0,
|
|
161
|
+
"add": 61,
|
|
162
|
+
"expected": "11:01"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"description": "add more than two hours with carry",
|
|
166
|
+
"hour": 0,
|
|
167
|
+
"minute": 45,
|
|
168
|
+
"add": 160,
|
|
169
|
+
"expected": "03:25"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"description": "add across midnight",
|
|
173
|
+
"hour": 23,
|
|
174
|
+
"minute": 59,
|
|
175
|
+
"add": 2,
|
|
176
|
+
"expected": "00:01"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"description": "add more than one day (1500 min = 25 hrs)",
|
|
180
|
+
"hour": 5,
|
|
181
|
+
"minute": 32,
|
|
182
|
+
"add": 1500,
|
|
183
|
+
"expected": "06:32"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"description": "add more than two days",
|
|
187
|
+
"hour": 1,
|
|
188
|
+
"minute": 1,
|
|
189
|
+
"add": 3500,
|
|
190
|
+
"expected": "11:21"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"description": "subtract minutes",
|
|
194
|
+
"hour": 10,
|
|
195
|
+
"minute": 3,
|
|
196
|
+
"add": -3,
|
|
197
|
+
"expected": "10:00"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"description": "subtract to previous hour",
|
|
201
|
+
"hour": 10,
|
|
202
|
+
"minute": 3,
|
|
203
|
+
"add": -30,
|
|
204
|
+
"expected": "09:33"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"description": "subtract more than an hour",
|
|
208
|
+
"hour": 10,
|
|
209
|
+
"minute": 3,
|
|
210
|
+
"add": -70,
|
|
211
|
+
"expected": "08:53"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"description": "subtract across midnight",
|
|
215
|
+
"hour": 0,
|
|
216
|
+
"minute": 3,
|
|
217
|
+
"add": -4,
|
|
218
|
+
"expected": "23:59"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"description": "subtract more than two hours",
|
|
222
|
+
"hour": 0,
|
|
223
|
+
"minute": 0,
|
|
224
|
+
"add": -160,
|
|
225
|
+
"expected": "21:20"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"description": "subtract more than two hours with borrow",
|
|
229
|
+
"hour": 6,
|
|
230
|
+
"minute": 15,
|
|
231
|
+
"add": -160,
|
|
232
|
+
"expected": "03:35"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"description": "subtract more than one day (1500 min = 25 hrs)",
|
|
236
|
+
"hour": 5,
|
|
237
|
+
"minute": 32,
|
|
238
|
+
"add": -1500,
|
|
239
|
+
"expected": "04:32"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"description": "subtract more than two days",
|
|
243
|
+
"hour": 2,
|
|
244
|
+
"minute": 20,
|
|
245
|
+
"add": -3000,
|
|
246
|
+
"expected": "00:20"
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
"equal": {
|
|
251
|
+
"description": [
|
|
252
|
+
"Construct two separate clocks, set times, test if they are equal."
|
|
253
|
+
],
|
|
254
|
+
"cases": [
|
|
255
|
+
{
|
|
256
|
+
"description": "clocks with same time",
|
|
257
|
+
"clock1": {
|
|
258
|
+
"hour": 15,
|
|
259
|
+
"minute": 37
|
|
260
|
+
},
|
|
261
|
+
"clock2": {
|
|
262
|
+
"hour": 15,
|
|
263
|
+
"minute": 37
|
|
264
|
+
},
|
|
265
|
+
"expected": true
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"description": "clocks a minute apart",
|
|
269
|
+
"clock1": {
|
|
270
|
+
"hour": 15,
|
|
271
|
+
"minute": 36
|
|
272
|
+
},
|
|
273
|
+
"clock2": {
|
|
274
|
+
"hour": 15,
|
|
275
|
+
"minute": 37
|
|
276
|
+
},
|
|
277
|
+
"expected": false
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"description": "clocks an hour apart",
|
|
281
|
+
"clock1": {
|
|
282
|
+
"hour": 14,
|
|
283
|
+
"minute": 37
|
|
284
|
+
},
|
|
285
|
+
"clock2": {
|
|
286
|
+
"hour": 15,
|
|
287
|
+
"minute": 37
|
|
288
|
+
},
|
|
289
|
+
"expected": false
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"description": "clocks with hour overflow",
|
|
293
|
+
"clock1": {
|
|
294
|
+
"hour": 10,
|
|
295
|
+
"minute": 37
|
|
296
|
+
},
|
|
297
|
+
"clock2": {
|
|
298
|
+
"hour": 34,
|
|
299
|
+
"minute": 37
|
|
300
|
+
},
|
|
301
|
+
"expected": true
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"description": "clocks with hour overflow by several days",
|
|
305
|
+
"clock1": {
|
|
306
|
+
"hour": 3,
|
|
307
|
+
"minute": 11
|
|
308
|
+
},
|
|
309
|
+
"clock2": {
|
|
310
|
+
"hour": 99,
|
|
311
|
+
"minute": 11
|
|
312
|
+
},
|
|
313
|
+
"expected": true
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"description": "clocks with negative hour",
|
|
317
|
+
"clock1": {
|
|
318
|
+
"hour": 22,
|
|
319
|
+
"minute": 40
|
|
320
|
+
},
|
|
321
|
+
"clock2": {
|
|
322
|
+
"hour": -2,
|
|
323
|
+
"minute": 40
|
|
324
|
+
},
|
|
325
|
+
"expected": true
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"description": "clocks with negative hour that wraps",
|
|
329
|
+
"clock1": {
|
|
330
|
+
"hour": 17,
|
|
331
|
+
"minute": 3
|
|
332
|
+
},
|
|
333
|
+
"clock2": {
|
|
334
|
+
"hour": -31,
|
|
335
|
+
"minute": 3
|
|
336
|
+
},
|
|
337
|
+
"expected": true
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"description": "clocks with negative hour that wraps multiple times",
|
|
341
|
+
"clock1": {
|
|
342
|
+
"hour": 13,
|
|
343
|
+
"minute": 49
|
|
344
|
+
},
|
|
345
|
+
"clock2": {
|
|
346
|
+
"hour": -83,
|
|
347
|
+
"minute": 49
|
|
348
|
+
},
|
|
349
|
+
"expected": true
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"description": "clocks with minute overflow",
|
|
353
|
+
"clock1": {
|
|
354
|
+
"hour": 0,
|
|
355
|
+
"minute": 1
|
|
356
|
+
},
|
|
357
|
+
"clock2": {
|
|
358
|
+
"hour": 0,
|
|
359
|
+
"minute": 1441
|
|
360
|
+
},
|
|
361
|
+
"expected": true
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"description": "clocks with minute overflow by several days",
|
|
365
|
+
"clock1": {
|
|
366
|
+
"hour": 2,
|
|
367
|
+
"minute": 2
|
|
368
|
+
},
|
|
369
|
+
"clock2": {
|
|
370
|
+
"hour": 2,
|
|
371
|
+
"minute": 4322
|
|
372
|
+
},
|
|
373
|
+
"expected": true
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"description": "clocks with negative minute",
|
|
377
|
+
"clock1": {
|
|
378
|
+
"hour": 2,
|
|
379
|
+
"minute": 40
|
|
380
|
+
},
|
|
381
|
+
"clock2": {
|
|
382
|
+
"hour": 3,
|
|
383
|
+
"minute": -20
|
|
384
|
+
},
|
|
385
|
+
"expected": true
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"description": "clocks with negative minute that wraps",
|
|
389
|
+
"clock1": {
|
|
390
|
+
"hour": 4,
|
|
391
|
+
"minute": 10
|
|
392
|
+
},
|
|
393
|
+
"clock2": {
|
|
394
|
+
"hour": 5,
|
|
395
|
+
"minute": -1490
|
|
396
|
+
},
|
|
397
|
+
"expected": true
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
"description": "clocks with negative minute that wraps multiple times",
|
|
401
|
+
"clock1": {
|
|
402
|
+
"hour": 6,
|
|
403
|
+
"minute": 15
|
|
404
|
+
},
|
|
405
|
+
"clock2": {
|
|
406
|
+
"hour": 6,
|
|
407
|
+
"minute": -4305
|
|
408
|
+
},
|
|
409
|
+
"expected": true
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
"description": "clocks with negative hours and minutes",
|
|
413
|
+
"clock1": {
|
|
414
|
+
"hour": 7,
|
|
415
|
+
"minute": 32
|
|
416
|
+
},
|
|
417
|
+
"clock2": {
|
|
418
|
+
"hour": -12,
|
|
419
|
+
"minute": -268
|
|
420
|
+
},
|
|
421
|
+
"expected": true
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"description": "clocks with negative hours and minutes that wrap",
|
|
425
|
+
"clock1": {
|
|
426
|
+
"hour": 18,
|
|
427
|
+
"minute": 7
|
|
428
|
+
},
|
|
429
|
+
"clock2": {
|
|
430
|
+
"hour": -54,
|
|
431
|
+
"minute": -11513
|
|
432
|
+
},
|
|
433
|
+
"expected": true
|
|
434
|
+
}
|
|
435
|
+
]
|
|
436
|
+
}
|
|
437
|
+
}
|