trackler 2.2.1.19 → 2.2.1.20
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/lib/trackler/version.rb +1 -1
- data/tracks/bash/config.json +11 -0
- data/tracks/bash/docs/ABOUT.md +5 -0
- data/tracks/bash/exercises/word-count/README.md +24 -0
- data/tracks/bash/exercises/word-count/example.awk +12 -0
- data/tracks/bash/exercises/word-count/example.sh +21 -0
- data/tracks/bash/exercises/word-count/word_count_test.sh +84 -0
- data/tracks/coffeescript/config/maintainers.json +5 -11
- data/tracks/csharp/.travis.yml +3 -0
- data/tracks/delphi/config/maintainers.json +1 -1
- data/tracks/ecmascript/config.json +108 -108
- data/tracks/elixir/exercises/phone-number/phone_number_test.exs +21 -1
- data/tracks/factor/docs/SNIPPET.txt +5 -0
- data/tracks/idris/docs/TESTS.md +7 -0
- data/tracks/java/config.json +47 -47
- data/tracks/javascript/config.json +103 -103
- data/tracks/julia/config.json +121 -121
- data/tracks/julia/docs/RESOURCES.md +1 -1
- data/tracks/julia/docs/SNIPPET.txt +8 -0
- data/tracks/objective-c/config.json +2 -3
- data/tracks/objective-c/exercises/rna-transcription/RNATranscriptionExample.m +11 -32
- data/tracks/ocaml/.travis-ci.sh +1 -1
- data/tracks/ocaml/.travis.yml +1 -1
- data/tracks/ocaml/README.md +1 -1
- data/tracks/ocaml/exercises/change/test.ml +24 -21
- data/tracks/ocaml/tools/test-generator/src/codegen.ml +3 -7
- data/tracks/ocaml/tools/test-generator/src/controller.ml +4 -5
- data/tracks/ocaml/tools/test-generator/src/languages.ml +4 -3
- data/tracks/ocaml/tools/test-generator/src/model.ml +1 -2
- data/tracks/ocaml/tools/test-generator/src/ocaml_special_cases.ml +19 -18
- data/tracks/ocaml/tools/test-generator/src/parser.ml +12 -17
- data/tracks/ocaml/tools/test-generator/src/purescript_special_cases.ml +11 -3
- data/tracks/ocaml/tools/test-generator/test/codegen_test.ml +2 -3
- data/tracks/ocaml/tools/test-generator/test/ocaml_special_cases_test.ml +12 -4
- data/tracks/ocaml/tools/test-generator/test/parser_test.ml +10 -10
- data/tracks/purescript/exercises/bob/test/Main.purs +5 -5
- data/tracks/ruby/bin/generate +1 -1
- data/tracks/ruby/docs/24pullrequests.md +1 -1
- data/tracks/ruby/exercises/all-your-base/all_your_base_test.rb +24 -24
- data/tracks/ruby/exercises/alphametics/.meta/generator/test_template.erb +1 -1
- data/tracks/ruby/exercises/bowling/.meta/generator/test_template.erb +1 -1
- data/tracks/ruby/exercises/bowling/bowling_test.rb +10 -10
- data/tracks/ruby/exercises/change/.meta/.version +1 -1
- data/tracks/ruby/exercises/change/.meta/solutions/change.rb +1 -1
- data/tracks/ruby/exercises/change/change_test.rb +7 -2
- data/tracks/ruby/exercises/collatz-conjecture/.meta/generator/collatz_conjecture_case.rb +1 -1
- data/tracks/ruby/exercises/connect/.meta/generator/test_template.erb +1 -1
- data/tracks/ruby/exercises/dominoes/.meta/generator/test_template.erb +1 -1
- data/tracks/ruby/exercises/hamming/.meta/generator/hamming_case.rb +5 -1
- data/tracks/ruby/exercises/hamming/hamming_test.rb +7 -7
- data/tracks/ruby/exercises/leap/.meta/generator/test_template.erb +1 -1
- data/tracks/ruby/exercises/pangram/.meta/.version +1 -1
- data/tracks/ruby/exercises/pangram/.meta/solutions/pangram.rb +1 -1
- data/tracks/ruby/exercises/pangram/pangram_test.rb +3 -3
- data/tracks/ruby/exercises/rna-transcription/rna_transcription_test.rb +4 -4
- data/tracks/ruby/exercises/roman-numerals/.meta/solutions/roman_numerals.rb +1 -1
- data/tracks/ruby/exercises/space-age/.meta/generator/test_template.erb +1 -1
- data/tracks/ruby/lib/generator/test_template.erb +1 -1
- data/tracks/ruby/lib/generator/underscore.rb +1 -1
- data/tracks/ruby/lib/helper.rb +4 -4
- data/tracks/ruby/test/fixtures/{xruby → ruby}/exercises/alpha-beta/.meta/generator/test_template.erb +1 -1
- data/tracks/ruby/test/fixtures/{xruby → ruby}/exercises/alpha/.meta/.version +0 -0
- data/tracks/ruby/test/fixtures/{xruby → ruby}/exercises/alpha/.meta/generator/alpha_case.rb +0 -0
- data/tracks/ruby/test/fixtures/{xruby → ruby}/exercises/alpha/.meta/generator/test_template.erb +1 -1
- data/tracks/ruby/test/fixtures/{xruby → ruby}/exercises/alpha/.meta/solutions/alpha.rb +0 -0
- data/tracks/ruby/test/fixtures/{xruby → ruby}/exercises/beta/.meta/.version +0 -0
- data/tracks/ruby/test/fixtures/{xruby → ruby}/exercises/beta/.meta/generator/beta_case.rb +0 -0
- data/tracks/ruby/test/fixtures/{xruby → ruby}/lib/generator/test_template.erb +1 -1
- data/tracks/ruby/test/generator/files/track_files_test.rb +4 -4
- data/tracks/ruby/test/generator/implementation_test.rb +1 -1
- data/tracks/ruby/test/generator/template_values_test.rb +1 -1
- data/tracks/scala/exercises/rail-fence-cipher/src/main/scala/{RailFenceCipher.scala → .keep} +0 -0
- data/tracks/scala/exercises/rail-fence-cipher/src/test/scala/RailFenceCipherTest.scala +16 -23
- data/tracks/scala/exercises/say/src/test/scala/SayTest.scala +20 -33
- data/tracks/scala/testgen/src/main/scala/RailFenceCipherTestGenerator.scala +9 -53
- data/tracks/scala/testgen/src/main/scala/SayTestGenerator.scala +50 -0
- data/tracks/sml/bin/generate +1 -1
- data/tracks/sml/config.json +30 -0
- data/tracks/sml/exercises/all-your-base/README.md +61 -0
- data/tracks/sml/exercises/all-your-base/all-your-base.sml +2 -0
- data/tracks/sml/exercises/all-your-base/example.sml +36 -0
- data/tracks/sml/exercises/all-your-base/test.sml +75 -0
- data/tracks/sml/exercises/all-your-base/testlib.sml +159 -0
- data/tracks/sml/exercises/difference-of-squares/example.sml +3 -6
- data/tracks/sml/exercises/list-ops/README.md +40 -0
- data/tracks/sml/exercises/list-ops/example.sml +34 -0
- data/tracks/sml/exercises/list-ops/list-ops.sml +23 -0
- data/tracks/sml/exercises/list-ops/test.sml +85 -0
- data/tracks/sml/exercises/list-ops/testlib.sml +159 -0
- data/tracks/sml/exercises/prime-factors/README.md +66 -0
- data/tracks/sml/exercises/prime-factors/example.sml +15 -0
- data/tracks/sml/exercises/prime-factors/prime-factors.sml +2 -0
- data/tracks/sml/exercises/prime-factors/test.sml +35 -0
- data/tracks/sml/exercises/prime-factors/testlib.sml +159 -0
- metadata +34 -11
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import java.io.File
|
|
2
|
+
|
|
3
|
+
import PrimeFactorsTestGenerator.toString
|
|
4
|
+
import testgen.TestSuiteBuilder.{sutArgs, _}
|
|
5
|
+
import testgen._
|
|
6
|
+
|
|
7
|
+
object SayTestGenerator {
|
|
8
|
+
def main(args: Array[String]): Unit = {
|
|
9
|
+
val file = new File("src/main/resources/say.json")
|
|
10
|
+
|
|
11
|
+
def toString(expected: CanonicalDataParser.Expected): String = {
|
|
12
|
+
expected match {
|
|
13
|
+
case Left(_) => "None"
|
|
14
|
+
case Right(-1) => "None"
|
|
15
|
+
case Right(n) => s"""Some("$n")"""
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def sutArgs(parseResult: CanonicalDataParser.ParseResult, argNames: String*): String =
|
|
20
|
+
argNames map (name => toArgString(parseResult(name))) mkString ", "
|
|
21
|
+
|
|
22
|
+
def toArgString(any: Any): String = {
|
|
23
|
+
any match {
|
|
24
|
+
case lstr: Long =>
|
|
25
|
+
lstr.toString + "l"
|
|
26
|
+
case istr: Integer =>
|
|
27
|
+
istr.toString
|
|
28
|
+
case dstr: Double =>
|
|
29
|
+
dstr.toLong + "l"
|
|
30
|
+
case _ => any.toString
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def fromLabeledTest(argNames: String*): ToTestCaseData =
|
|
35
|
+
withLabeledTest { sut =>
|
|
36
|
+
labeledTest =>
|
|
37
|
+
val args = sutArgs(labeledTest.result, argNames: _*)
|
|
38
|
+
val property = labeledTest.property
|
|
39
|
+
val sutCall =
|
|
40
|
+
s"""$sut.inEnglish($args)"""
|
|
41
|
+
val expected = toString(labeledTest.expected)
|
|
42
|
+
TestCaseData(labeledTest.description, sutCall, expected)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
val code = TestSuiteBuilder.build(file, fromLabeledTest("input"))
|
|
46
|
+
println(s"-------------")
|
|
47
|
+
println(code)
|
|
48
|
+
println(s"-------------")
|
|
49
|
+
}
|
|
50
|
+
}
|
data/tracks/sml/bin/generate
CHANGED
|
@@ -212,8 +212,8 @@ def generate_test_content(data, signature):
|
|
|
212
212
|
return '\n'.join([
|
|
213
213
|
'(* version %s *)' % data['version'],
|
|
214
214
|
'',
|
|
215
|
-
'use "%s.sml";' % data['exercise'],
|
|
216
215
|
'use "testlib.sml";',
|
|
216
|
+
'use "%s.sml";' % data['exercise'],
|
|
217
217
|
'',
|
|
218
218
|
'infixr |>',
|
|
219
219
|
'fun x |> f = f x',
|
data/tracks/sml/config.json
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
"active": false,
|
|
5
5
|
"test_pattern": "test[.]sml$",
|
|
6
6
|
"exercises": [
|
|
7
|
+
{
|
|
8
|
+
"uuid": "7e1d0e9a-435d-4dc6-8867-2fc4f9b7fe6f",
|
|
9
|
+
"slug": "all-your-base",
|
|
10
|
+
"core": false,
|
|
11
|
+
"unlocked_by": null,
|
|
12
|
+
"difficulty": 1,
|
|
13
|
+
"topics": [
|
|
14
|
+
|
|
15
|
+
]
|
|
16
|
+
},
|
|
7
17
|
{
|
|
8
18
|
"uuid": "4450455d-0786-6f80-7672-48368a115df16e0aa7e",
|
|
9
19
|
"slug": "hello-world",
|
|
@@ -134,6 +144,26 @@
|
|
|
134
144
|
|
|
135
145
|
]
|
|
136
146
|
},
|
|
147
|
+
{
|
|
148
|
+
"uuid": "6c191163-0a98-2380-debc-75144c1787f3853e868",
|
|
149
|
+
"slug": "prime-factors",
|
|
150
|
+
"core": false,
|
|
151
|
+
"unlocked_by": null,
|
|
152
|
+
"difficulty": 1,
|
|
153
|
+
"topics": [
|
|
154
|
+
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"uuid": "c8b94fa9-d758-4dca-b7fc-7a130061e064",
|
|
159
|
+
"slug": "list-ops",
|
|
160
|
+
"core": false,
|
|
161
|
+
"unlocked_by": null,
|
|
162
|
+
"difficulty": 1,
|
|
163
|
+
"topics": [
|
|
164
|
+
|
|
165
|
+
]
|
|
166
|
+
},
|
|
137
167
|
{
|
|
138
168
|
"uuid": "225cfd7d-81a3-4a58-b1aa-1de2e40e7a93",
|
|
139
169
|
"slug": "binary",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# All Your Base
|
|
2
|
+
|
|
3
|
+
Convert a number, represented as a sequence of digits in one base, to any other base.
|
|
4
|
+
|
|
5
|
+
Implement general base conversion. Given a number in base a, represented as a sequence of digits, convert it to base b.
|
|
6
|
+
|
|
7
|
+
Note
|
|
8
|
+
|
|
9
|
+
Try to implement the conversion yourself. Do not use something else to perform the conversion for you.
|
|
10
|
+
|
|
11
|
+
About Positional Notation:
|
|
12
|
+
|
|
13
|
+
In positional notation, a number in base b can be understood as a linear combination of powers of b.
|
|
14
|
+
|
|
15
|
+
The number 42, in base 10, means:
|
|
16
|
+
|
|
17
|
+
(4 * 10^1) + (2 * 10^0)
|
|
18
|
+
|
|
19
|
+
The number 101010, in base 2, means:
|
|
20
|
+
|
|
21
|
+
(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)
|
|
22
|
+
|
|
23
|
+
The number 1120, in base 3, means:
|
|
24
|
+
|
|
25
|
+
(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)
|
|
26
|
+
|
|
27
|
+
I think you got the idea!
|
|
28
|
+
|
|
29
|
+
Yes. Those three numbers above are exactly the same. Congratulations!
|
|
30
|
+
|
|
31
|
+
## Loading your exercise implementation in PolyML
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
$ poly --use {exercise}.sml
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
$ poly
|
|
41
|
+
> use "{exercise}.sml";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Note:** You have to replace {exercise}.
|
|
45
|
+
|
|
46
|
+
## Running the tests
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
$ poly -q --use test.sml
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Feedback, Issues, Pull Requests
|
|
53
|
+
|
|
54
|
+
The [exercism/sml](https://github.com/exercism/sml) repository on
|
|
55
|
+
GitHub is the home for all of the Standard ML exercises.
|
|
56
|
+
|
|
57
|
+
If you have feedback about an exercise, or want to help implementing a new
|
|
58
|
+
one, head over there and create an issue. We'll do our best to help you!
|
|
59
|
+
|
|
60
|
+
## Submitting Incomplete Solutions
|
|
61
|
+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
fun rebase (inBase, digits, outBase) = let
|
|
2
|
+
fun fromBase base = let
|
|
3
|
+
fun f (x, NONE ) = NONE
|
|
4
|
+
| f (x, SOME n) =
|
|
5
|
+
if x >= 0 andalso x < base then SOME(n * base + x)
|
|
6
|
+
else NONE
|
|
7
|
+
in
|
|
8
|
+
foldl f (SOME 0)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
fun toBase base = let
|
|
12
|
+
infix divMod
|
|
13
|
+
fun a divMod b = (a div b, a mod b)
|
|
14
|
+
|
|
15
|
+
fun swap (a, b) = (b, a)
|
|
16
|
+
|
|
17
|
+
fun unfold f b =
|
|
18
|
+
case f b of
|
|
19
|
+
SOME(a, b') => a :: unfold f b'
|
|
20
|
+
| NONE => []
|
|
21
|
+
|
|
22
|
+
fun f 0 = NONE
|
|
23
|
+
| f x = SOME (swap (x divMod base))
|
|
24
|
+
in
|
|
25
|
+
rev o (unfold f)
|
|
26
|
+
end
|
|
27
|
+
in
|
|
28
|
+
if inBase < 2
|
|
29
|
+
orelse outBase < 2
|
|
30
|
+
orelse (List.length digits) = 0
|
|
31
|
+
orelse (List.length digits) = (List.length (List.filter (fn x => x = 0) digits))
|
|
32
|
+
then NONE
|
|
33
|
+
else case fromBase inBase digits of
|
|
34
|
+
SOME v => SOME (toBase outBase v)
|
|
35
|
+
| NONE => NONE
|
|
36
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
(* version 1.1.0 *)
|
|
2
|
+
|
|
3
|
+
use "all-your-base.sml";
|
|
4
|
+
use "testlib.sml";
|
|
5
|
+
|
|
6
|
+
infixr |>
|
|
7
|
+
fun x |> f = f x
|
|
8
|
+
|
|
9
|
+
val testsuite =
|
|
10
|
+
describe "all-your-base" [
|
|
11
|
+
test "single bit one to decimal"
|
|
12
|
+
(fn _ => rebase (2, [1], 10) |> Expect.equalTo (SOME [1])),
|
|
13
|
+
|
|
14
|
+
test "binary to single decimal"
|
|
15
|
+
(fn _ => rebase (2, [1, 0, 1], 10) |> Expect.equalTo (SOME [5])),
|
|
16
|
+
|
|
17
|
+
test "single decimal to binary"
|
|
18
|
+
(fn _ => rebase (10, [5], 2) |> Expect.equalTo (SOME [1, 0, 1])),
|
|
19
|
+
|
|
20
|
+
test "binary to multiple decimal"
|
|
21
|
+
(fn _ => rebase (2, [1, 0, 1, 0, 1, 0], 10) |> Expect.equalTo (SOME [4, 2])),
|
|
22
|
+
|
|
23
|
+
test "decimal to binary"
|
|
24
|
+
(fn _ => rebase (10, [4, 2], 2) |> Expect.equalTo (SOME [1, 0, 1, 0, 1, 0])),
|
|
25
|
+
|
|
26
|
+
test "trinary to hexadecimal"
|
|
27
|
+
(fn _ => rebase (3, [1, 1, 2, 0], 16) |> Expect.equalTo (SOME [2, 10])),
|
|
28
|
+
|
|
29
|
+
test "hexadecimal to trinary"
|
|
30
|
+
(fn _ => rebase (16, [2, 10], 3) |> Expect.equalTo (SOME [1, 1, 2, 0])),
|
|
31
|
+
|
|
32
|
+
test "15-bit integer"
|
|
33
|
+
(fn _ => rebase (97, [3, 46, 60], 73) |> Expect.equalTo (SOME [6, 10, 45])),
|
|
34
|
+
|
|
35
|
+
test "empty list"
|
|
36
|
+
(fn _ => rebase (2, [], 10) |> Expect.equalTo NONE),
|
|
37
|
+
|
|
38
|
+
test "single zero"
|
|
39
|
+
(fn _ => rebase (10, [0], 2) |> Expect.equalTo NONE),
|
|
40
|
+
|
|
41
|
+
test "multiple zeros"
|
|
42
|
+
(fn _ => rebase (10, [0, 0, 0], 2) |> Expect.equalTo NONE),
|
|
43
|
+
|
|
44
|
+
test "leading zeros"
|
|
45
|
+
(fn _ => rebase (7, [0, 6, 0], 10) |> Expect.equalTo (SOME [4, 2])),
|
|
46
|
+
|
|
47
|
+
test "first base is one"
|
|
48
|
+
(fn _ => rebase (1, [], 10) |> Expect.equalTo NONE),
|
|
49
|
+
|
|
50
|
+
test "first base is zero"
|
|
51
|
+
(fn _ => rebase (0, [], 10) |> Expect.equalTo NONE),
|
|
52
|
+
|
|
53
|
+
test "first base is negative"
|
|
54
|
+
(fn _ => rebase (~2, [1], 10) |> Expect.equalTo NONE),
|
|
55
|
+
|
|
56
|
+
test "negative digit"
|
|
57
|
+
(fn _ => rebase (2, [1, ~1, 1, 0, 1, 0], 10) |> Expect.equalTo NONE),
|
|
58
|
+
|
|
59
|
+
test "invalid positive digit"
|
|
60
|
+
(fn _ => rebase (2, [1, 2, 1, 0, 1, 0], 10) |> Expect.equalTo NONE),
|
|
61
|
+
|
|
62
|
+
test "second base is one"
|
|
63
|
+
(fn _ => rebase (2, [1, 0, 1, 0, 1, 0], 1) |> Expect.equalTo NONE),
|
|
64
|
+
|
|
65
|
+
test "second base is zero"
|
|
66
|
+
(fn _ => rebase (10, [7], 0) |> Expect.equalTo NONE),
|
|
67
|
+
|
|
68
|
+
test "second base is negative"
|
|
69
|
+
(fn _ => rebase (2, [1], ~7) |> Expect.equalTo NONE),
|
|
70
|
+
|
|
71
|
+
test "both bases are negative"
|
|
72
|
+
(fn _ => rebase (~2, [1], ~7) |> Expect.equalTo NONE)
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
val _ = Test.run testsuite
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
structure Expect =
|
|
2
|
+
struct
|
|
3
|
+
datatype expectation = Pass | Fail of string * string
|
|
4
|
+
|
|
5
|
+
local
|
|
6
|
+
fun failEq b a =
|
|
7
|
+
Fail ("Expected: " ^ b, "Got: " ^ a)
|
|
8
|
+
|
|
9
|
+
fun failExn b a =
|
|
10
|
+
Fail ("Expected: " ^ b, "Raised: " ^ a)
|
|
11
|
+
|
|
12
|
+
fun exnName (e: exn): string = General.exnName e
|
|
13
|
+
in
|
|
14
|
+
fun truthy a =
|
|
15
|
+
if a
|
|
16
|
+
then Pass
|
|
17
|
+
else failEq "true" "false"
|
|
18
|
+
|
|
19
|
+
fun falsy a =
|
|
20
|
+
if a
|
|
21
|
+
then failEq "false" "true"
|
|
22
|
+
else Pass
|
|
23
|
+
|
|
24
|
+
fun equalTo b a =
|
|
25
|
+
if a = b
|
|
26
|
+
then Pass
|
|
27
|
+
else failEq (PolyML.makestring b) (PolyML.makestring a)
|
|
28
|
+
|
|
29
|
+
fun nearTo b a =
|
|
30
|
+
if Real.== (a, b)
|
|
31
|
+
then Pass
|
|
32
|
+
else failEq (Real.toString b) (Real.toString a)
|
|
33
|
+
|
|
34
|
+
fun anyError f =
|
|
35
|
+
(
|
|
36
|
+
f ();
|
|
37
|
+
failExn "an exception" "Nothing"
|
|
38
|
+
) handle _ => Pass
|
|
39
|
+
|
|
40
|
+
fun error e f =
|
|
41
|
+
(
|
|
42
|
+
f ();
|
|
43
|
+
failExn (exnName e) "Nothing"
|
|
44
|
+
) handle e' => if exnMessage e' = exnMessage e
|
|
45
|
+
then Pass
|
|
46
|
+
else failExn (exnMessage e) (exnMessage e')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
structure TermColor =
|
|
51
|
+
struct
|
|
52
|
+
datatype color = Red | Green | Yellow | Normal
|
|
53
|
+
|
|
54
|
+
fun f Red = "\027[31m"
|
|
55
|
+
| f Green = "\027[32m"
|
|
56
|
+
| f Yellow = "\027[33m"
|
|
57
|
+
| f Normal = "\027[0m"
|
|
58
|
+
|
|
59
|
+
fun colorize color s = (f color) ^ s ^ (f Normal)
|
|
60
|
+
|
|
61
|
+
val redit = colorize Red
|
|
62
|
+
|
|
63
|
+
val greenit = colorize Green
|
|
64
|
+
|
|
65
|
+
val yellowit = colorize Yellow
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
structure Test =
|
|
69
|
+
struct
|
|
70
|
+
datatype testnode = TestGroup of string * testnode list
|
|
71
|
+
| Test of string * (unit -> Expect.expectation)
|
|
72
|
+
|
|
73
|
+
local
|
|
74
|
+
datatype evaluation = Success of string
|
|
75
|
+
| Failure of string * string * string
|
|
76
|
+
| Error of string * string
|
|
77
|
+
|
|
78
|
+
fun indent n s = (implode (List.tabulate (n, fn _ => #" "))) ^ s
|
|
79
|
+
|
|
80
|
+
fun fmt indentlvl ev =
|
|
81
|
+
let
|
|
82
|
+
val check = TermColor.greenit "\226\156\148 " (* ✔ *)
|
|
83
|
+
val cross = TermColor.redit "\226\156\150 " (* ✖ *)
|
|
84
|
+
val indentlvl = indentlvl * 2
|
|
85
|
+
in
|
|
86
|
+
case ev of
|
|
87
|
+
Success descr => indent indentlvl (check ^ descr)
|
|
88
|
+
| Failure (descr, exp, got) =>
|
|
89
|
+
String.concatWith "\n" [indent indentlvl (cross ^ descr),
|
|
90
|
+
indent (indentlvl + 2) exp,
|
|
91
|
+
indent (indentlvl + 2) got]
|
|
92
|
+
| Error (descr, reason) =>
|
|
93
|
+
String.concatWith "\n" [indent indentlvl (cross ^ descr),
|
|
94
|
+
indent (indentlvl + 2) (TermColor.redit reason)]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
fun eval (TestGroup _) = raise Fail "Only a 'Test' can be evaluated"
|
|
98
|
+
| eval (Test (descr, thunk)) =
|
|
99
|
+
(
|
|
100
|
+
case thunk () of
|
|
101
|
+
Expect.Pass => ((1, 0, 0), Success descr)
|
|
102
|
+
| Expect.Fail (s, s') => ((0, 1, 0), Failure (descr, s, s'))
|
|
103
|
+
)
|
|
104
|
+
handle e => ((0, 0, 1), Error (descr, "Unexpected error: " ^ exnMessage e))
|
|
105
|
+
|
|
106
|
+
fun flatten depth testnode =
|
|
107
|
+
let
|
|
108
|
+
fun sum (x, y, z) (a, b, c) = (x + a, y + b, z + c)
|
|
109
|
+
|
|
110
|
+
fun aux (t, (counter, acc)) =
|
|
111
|
+
let
|
|
112
|
+
val (counter', texts) = flatten (depth + 1) t
|
|
113
|
+
in
|
|
114
|
+
(sum counter' counter, texts :: acc)
|
|
115
|
+
end
|
|
116
|
+
in
|
|
117
|
+
case testnode of
|
|
118
|
+
TestGroup (descr, ts) =>
|
|
119
|
+
let
|
|
120
|
+
val (counter, texts) = foldr aux ((0, 0, 0), []) ts
|
|
121
|
+
in
|
|
122
|
+
(counter, (indent (depth * 2) descr) :: List.concat texts)
|
|
123
|
+
end
|
|
124
|
+
| Test _ =>
|
|
125
|
+
let
|
|
126
|
+
val (counter, evaluation) = eval testnode
|
|
127
|
+
in
|
|
128
|
+
(counter, [fmt depth evaluation])
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
fun println s = print (s ^ "\n")
|
|
133
|
+
in
|
|
134
|
+
fun run suite =
|
|
135
|
+
let
|
|
136
|
+
val ((succeeded, failed, errored), texts) = flatten 0 suite
|
|
137
|
+
|
|
138
|
+
val summary = String.concatWith ", " [
|
|
139
|
+
TermColor.greenit ((Int.toString succeeded) ^ " passed"),
|
|
140
|
+
TermColor.redit ((Int.toString failed) ^ " failed"),
|
|
141
|
+
TermColor.redit ((Int.toString errored) ^ " errored"),
|
|
142
|
+
(Int.toString (succeeded + failed + errored)) ^ " total"
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
val status = if failed = 0 andalso errored = 0
|
|
146
|
+
then OS.Process.success
|
|
147
|
+
else OS.Process.failure
|
|
148
|
+
|
|
149
|
+
in
|
|
150
|
+
List.app println texts;
|
|
151
|
+
println "";
|
|
152
|
+
println ("Tests: " ^ summary);
|
|
153
|
+
OS.Process.exit status
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
fun describe description tests = Test.TestGroup (description, tests)
|
|
159
|
+
fun test description thunk = Test.Test (description, thunk)
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
local
|
|
2
|
-
val identity = fn x => x
|
|
3
|
-
fun range n = List.tabulate (n, identity)
|
|
4
2
|
fun square n = n * n
|
|
5
|
-
|
|
6
|
-
fun inc n = n + 1
|
|
3
|
+
fun sum n = n * (n + 1) div 2
|
|
7
4
|
in
|
|
8
|
-
val squareOfSum = square o sum
|
|
9
|
-
fun sumOfSquares n =
|
|
5
|
+
val squareOfSum = square o sum
|
|
6
|
+
fun sumOfSquares n = n * (n + 1) * (2 * n + 1) div 6
|
|
10
7
|
fun differenceOfSquares n = squareOfSum n - sumOfSquares n
|
|
11
8
|
end
|