trackler 2.0.3.8 → 2.0.3.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|