Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
*.swp
**/packages/
.buildlog
.DS_Store
.project
bin/configlet
bin/configlet.exe

Expand All @@ -22,6 +18,4 @@ doc/
*.js.map
*.info.json

## IDE files/folders
/.idea/
/.vscode/
# editor files should go into your personal git ignore file ~/.config/git/ignore
70 changes: 70 additions & 0 deletions bin/test_one_exercise
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
#
# For a given exercise, validate the tests.
# The directory of the exercise to be tested is passed as $1

set -o errexit
set -o nounset

die() { echo "$*" >&2; exit 1; }

(( $# == 1 )) || die "usage: $0 path/to/exercise"
cd "$1" || die "Cannot cd to $1"

config=./.meta/config.json
[[ -f ${config} ]] || die "no $config file: you're in the wrong directory"

command -v jq >/dev/null || die "You must install jq to run tests"

exercise=${1%/} # strip a trailing slash, if there is one
exercise=${exercise##*/} # remove any leading path

stub="$(jq -r '.files.solution[0]' "$config")"
[[ -f ${stub} ]] || die "Missing stub file for $exercise"

tests=$(jq -r '.files.test[0]' "$config")
[[ -f ${tests} ]] || die "Missing tests file for $exercise"

solution=$(jq -r '.files.example[0] // .files.exemplar[0]' "$config")
[[ -f ${solution} ]] || die "Missing solution file for $exercise"

invalidator=$(jq -r '.files.invalidator[0]' "$config")
[[ -f ${invalidator} ]] || die "Missing invalidator file for $exercise"

# this is usually empty but might have multiple files in it
readarray -t editor_files < <(jq -r '.files.editor[]?' "$config")

# Check that the test file only leaves one test unskipped.
# There are a few exercise where the test file is structured a little differently.
# Concept exercises have no skips.
if ! [[ $PWD =~ exercises/concept/ ]]; then
expected=1
case "${exercise}" in
hello-world) : ;; # don't check
*) num_tests=$(grep -c '^ *test(' "${tests}" || :)
num_skips=$(grep -c ' skip: true' "${tests}" || :)
(( num_skips == num_tests - expected )) || die "$exercise: there should be exactly ${expected} un-skipped test."
esac
fi

cleanup() {
rm -rf "${tmpdir}"
}
trap cleanup EXIT

# Create implementation file from example
tmpdir=$(mktemp -d)

dest="${tmpdir}/${stub}"
mkdir -p "$(dirname "${dest}")"
cp "${solution}" "${dest}"

for file in "${tests}" "${invalidator}" "${editor_files[@]}"; do
dest="${tmpdir}/$(dirname "${file}")"
mkdir -p "${dest}"
cp "$file" "${dest}"
done

# Run the tests
cd "${tmpdir}"
dart run test -r github --run-skipped
3 changes: 3 additions & 0 deletions exercises/practice/flower-field/.meta/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ description = "cross"

[dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8]
description = "large garden"

[6e4ac13a-3e43-4728-a2e3-3551d4b1a996]
description = "multiple adjacent flowers"
7 changes: 6 additions & 1 deletion exercises/practice/flower-field/test/flower_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,14 @@ void main() {
expect(result, equals(<String>[' 2*2 ', '25*52', '*****', '25*52', ' 2*2 ']));
}, skip: true);

test('large gardem', () {
test('large garden', () {
final result = FlowerField(<String>[' * * ', ' * ', ' * ', ' * *', ' * * ', ' ']).annotated;
expect(result, equals(<String>['1*22*1', '12*322', ' 123*2', '112*4*', '1*22*2', '111111']));
}, skip: true);

test('multiple adjacent flowers', () {
final result = FlowerField(<String>[' ** ']).annotated;
expect(result, equals(<String>['1**1']));
}, skip: true);
});
}
18 changes: 18 additions & 0 deletions exercises/practice/forth/.meta/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ description = "addition -> errors if there is nothing on the stack"
[06efb9a4-817a-435e-b509-06166993c1b8]
description = "addition -> errors if there is only one value on the stack"

[1e07a098-c5fa-4c66-97b2-3c81205dbc2f]
description = "addition -> more than two values on the stack"

[09687c99-7bbc-44af-8526-e402f997ccbf]
description = "subtraction -> can subtract two numbers"

Expand All @@ -33,6 +36,9 @@ description = "subtraction -> errors if there is nothing on the stack"
[b3cee1b2-9159-418a-b00d-a1bb3765c23b]
description = "subtraction -> errors if there is only one value on the stack"

[2c8cc5ed-da97-4cb1-8b98-fa7b526644f4]
description = "subtraction -> more than two values on the stack"

[5df0ceb5-922e-401f-974d-8287427dbf21]
description = "multiplication -> can multiply two numbers"

Expand All @@ -42,6 +48,9 @@ description = "multiplication -> errors if there is nothing on the stack"
[8ba4b432-9f94-41e0-8fae-3b3712bd51b3]
description = "multiplication -> errors if there is only one value on the stack"

[5cd085b5-deb1-43cc-9c17-6b1c38bc9970]
description = "multiplication -> more than two values on the stack"

[e74c2204-b057-4cff-9aa9-31c7c97a93f5]
description = "division -> can divide two numbers"

Expand All @@ -57,12 +66,21 @@ description = "division -> errors if there is nothing on the stack"
[d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d]
description = "division -> errors if there is only one value on the stack"

[f224f3e0-b6b6-4864-81de-9769ecefa03f]
description = "division -> more than two values on the stack"

[ee28d729-6692-4a30-b9be-0d830c52a68c]
description = "combined arithmetic -> addition and subtraction"

[40b197da-fa4b-4aca-a50b-f000d19422c1]
description = "combined arithmetic -> multiplication and division"

[f749b540-53aa-458e-87ec-a70797eddbcb]
description = "combined arithmetic -> multiplication and addition"

[c8e5a4c2-f9bf-4805-9a35-3c3314e4989a]
description = "combined arithmetic -> addition and multiplication"

[c5758235-6eef-4bf6-ab62-c878e50b9957]
description = "dup -> copies a value on the stack"

Expand Down
36 changes: 36 additions & 0 deletions exercises/practice/forth/test/forth_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ void addition() {
throwsEmptyStack,
);
}, skip: true);

test('more than two values on the stack', () {
var forth = Forth();
forth.evaluate('1 2 3 +');
expect(forth.stack, equals(<int>[1, 5]));
}, skip: true);
}

void subtraction() {
Expand All @@ -80,6 +86,12 @@ void subtraction() {
throwsEmptyStack,
);
}, skip: true);

test('more than two values on the stack', () {
var forth = Forth();
forth.evaluate('1 12 3 -');
expect(forth.stack, equals(<int>[1, 9]));
}, skip: true);
}

void multiplication() {
Expand All @@ -104,6 +116,12 @@ void multiplication() {
throwsEmptyStack,
);
}, skip: true);

test('more than two values on the stack', () {
var forth = Forth();
forth.evaluate('1 2 3 *');
expect(forth.stack, equals(<int>[1, 6]));
}, skip: true);
}

void division() {
Expand Down Expand Up @@ -142,6 +160,12 @@ void division() {
throwsEmptyStack,
);
}, skip: true);

test('more than two values on the stack', () {
var forth = Forth();
forth.evaluate('1 12 3 /');
expect(forth.stack, equals(<int>[1, 4]));
}, skip: true);
}

void combinedArithmetic() {
Expand All @@ -156,6 +180,18 @@ void combinedArithmetic() {
forth.evaluate('2 4 * 3 /');
expect(forth.stack, equals(<int>[2]));
}, skip: true);

test('multiplication and addition', () {
var forth = Forth();
forth.evaluate('1 3 4 * +');
expect(forth.stack, equals(<int>[13]));
}, skip: true);

test('addition and multiplication', () {
var forth = Forth();
forth.evaluate('1 3 4 + *');
expect(forth.stack, equals(<int>[7]));
}, skip: true);
}

void dup() {
Expand Down
6 changes: 6 additions & 0 deletions exercises/practice/isbn-verifier/.meta/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ description = "invalid character in isbn is not treated as zero"
[28025280-2c39-4092-9719-f3234b89c627]
description = "X is only valid as a check digit"

[8005b57f-f194-44ee-88d2-a77ac4142591]
description = "only one check digit is allowed"

[fdb14c99-4cf8-43c5-b06d-eb1638eff343]
description = "X is not substituted by the value 10"

[f6294e61-7e79-46b3-977b-f48789a4945b]
description = "valid isbn without separating dashes"

Expand Down
10 changes: 10 additions & 0 deletions exercises/practice/isbn-verifier/test/isbn_verifier_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ void main() {
expect(result, equals(false));
}, skip: true);

test('only one check digit is allowed', () {
final result = isValid('3-598-21508-96');
expect(result, equals(false));
}, skip: true);

test('X is not substituted by the value 10', () {
final result = isValid('3-598-2X507-5');
expect(result, equals(false));
}, skip: true);

test('valid isbn without separating dashes', () {
final result = isValid('3598215088');
expect(result, equals(true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class LargestSeriesProduct {
throw new ArgumentError('span must not be negative');
}
if (span > number.length) {
throw new ArgumentError('span must be smaller than string length');
throw new ArgumentError('span must not exceed string length');
}

List<int> digits = number.codeUnits.map((cp) => cp - 48).toList();
Expand Down
10 changes: 10 additions & 0 deletions exercises/practice/largest-series-product/.meta/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ description = "reports zero if all spans include zero"

[5d81aaf7-4f67-4125-bf33-11493cc7eab7]
description = "rejects span longer than string length"
include = false

[0ae1ce53-d9ba-41bb-827f-2fceb64f058b]
description = "rejects span longer than string length"
reimplements = "5d81aaf7-4f67-4125-bf33-11493cc7eab7"

[06bc8b90-0c51-4c54-ac22-3ec3893a079e]
description = "reports 1 for empty string and empty product (0 span)"
Expand All @@ -47,6 +52,11 @@ description = "reports 1 for nonempty string and empty product (0 span)"

[6d96c691-4374-4404-80ee-2ea8f3613dd4]
description = "rejects empty string and nonzero span"
include = false

[6cf66098-a6af-4223-aab1-26aeeefc7402]
description = "rejects empty string and nonzero span"
reimplements = "6d96c691-4374-4404-80ee-2ea8f3613dd4"

[7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74]
description = "rejects invalid character in digits"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ void main() {
test('rejects span longer than string length', () {
expect(
() => lsp.largestProduct("123", 4),
throwsA(isA<ArgumentError>()
.having((e) => e.message, 'message', contains('span must be smaller than string length'))));
throwsA(
isA<ArgumentError>().having((e) => e.message, 'message', contains('span must not exceed string length'))));
}, skip: true);

test('reports 1 for empty string and empty product (0 span)', () {
Expand All @@ -69,8 +69,8 @@ void main() {
test('rejects empty string and nonzero span', () {
expect(
() => lsp.largestProduct("", 1),
throwsA(isA<ArgumentError>()
.having((e) => e.message, 'message', contains('span must be smaller than string length'))));
throwsA(
isA<ArgumentError>().having((e) => e.message, 'message', contains('span must not exceed string length'))));
}, skip: true);

test('rejects invalid character in digits', () {
Expand Down
5 changes: 4 additions & 1 deletion exercises/practice/list-ops/.meta/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ reimplements = "be396a53-c074-4db3-8dd6-f7ed003cce7c"
description = "reverse the elements of the list -> empty list"

[fcc03d1e-42e0-4712-b689-d54ad761f360]
description = "reverse the elements of the list -> non-empty list"
description = "reverse the elements of the list -> non-empty even-length list"

[64d77184-5f74-4845-b158-545d2dd2df98]
description = "reverse the elements of the list -> non-empty odd-length list"

[40872990-b5b8-4cb8-9085-d91fc0d05d26]
description = "reverse the elements of the list -> list of lists is not flattened"
9 changes: 8 additions & 1 deletion exercises/practice/list-ops/test/list_ops_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,20 @@ void main() {
expect(result, equals([]));
}, skip: true);

test('non-empty list', () {
test('non-empty even-length list', () {
final list = [1, 3, 5, 7];
final result = list.reverse();
expect(result, equals([7, 5, 3, 1]));
expect(list, equals([1, 3, 5, 7]));
}, skip: true);

test('non-empty odd-length list', () {
final list = [1, 3, 5, 7, 9, 11, 13];
final result = list.reverse();
expect(result, equals([13, 11, 9, 7, 5, 3, 1]));
expect(list, equals([1, 3, 5, 7, 9, 11, 13]));
}, skip: true);

test('list of lists is not flattened', () {
final list = [
[1, 2],
Expand Down
3 changes: 3 additions & 0 deletions exercises/practice/perfect-numbers/.meta/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ description = "Abundant numbers -> Medium abundant number is classified correctl
[ec7792e6-8786-449c-b005-ce6dd89a772b]
description = "Abundant numbers -> Large abundant number is classified correctly"

[05f15b93-849c-45e9-9c7d-1ea131ef7d10]
description = "Abundant numbers -> Perfect square abundant number is classified correctly"

[e610fdc7-2b6e-43c3-a51c-b70fb37413ba]
description = "Deficient numbers -> Smallest prime deficient number is classified correctly"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ void main() {
final result = perfectNumbers.classify(33550335);
expect(result, equals(Classification.abundant));
}, skip: true);

test('Perfect square number is classified correctly', () {
final result = perfectNumbers.classify(196);
expect(result, equals(Classification.abundant));
}, skip: true);
});

group('Deficient numbers', () {
Expand Down
3 changes: 3 additions & 0 deletions exercises/practice/pig-latin/.meta/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ description = "first letter and ay are moved to the end of words that start with
[bce94a7a-a94e-4e2b-80f4-b2bb02e40f71]
description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with q without a following u"

[e59dbbe8-ccee-4619-a8e9-ce017489bfc0]
description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with consonant and vowel containing qu"

[c01e049a-e3e2-451c-bf8e-e2abb7e438b8]
description = "some letter clusters are treated like a single consonant -> word beginning with ch"

Expand Down
5 changes: 5 additions & 0 deletions exercises/practice/pig-latin/test/pig_latin_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ void main() {
final result = pigLatin.translate('qat');
expect(result, equals('atqay'));
}, skip: true);

test('word beginning with consonant and vowel containing qu', () {
final result = pigLatin.translate('liquid');
expect(result, equals('iquidlay'));
}, skip: true);
});

group('some letter clusters are treated like a single consonant', () {
Expand Down
Loading
Loading