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
107 changes: 107 additions & 0 deletions bin/verify-exercises
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env bash

# Synopsis:
# Verify that each exercise's example/exemplar solution passes the tests.
# You can either verify all exercises or a single exercise.

# Example: verify all exercises
# bin/verify-exercises

# Example: verify single exercise
# bin/verify-exercises two-fer

set -eo pipefail

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

required_tool() {
command -v "${1}" >/dev/null 2>&1 ||
die "${1} is required but not installed. Please install it and make sure it's in your PATH."
}

required_tool jq

FACTOR="${FACTOR:-factor}"
"${FACTOR}" -e='1 2 +' >/dev/null 2>&1||
die "Factor is required but '${FACTOR}' does not appear to be the Factor language.
Set the FACTOR environment variable to the path of the Factor executable."

copy_example_or_examplar_to_solution() {
jq -c '[.files.solution, .files.exemplar // .files.example] | transpose | map({src: .[1], dst: .[0]}) | .[]' .meta/config.json \
| while read -r src_and_dst; do
cp "$(jq -r '.src' <<< "${src_and_dst}")" "$(jq -r '.dst' <<< "${src_and_dst}")"
done
}

unskip_tests() {
jq -r '.files.test[]' .meta/config.json | while read -r test_file; do
sed -i '/STOP-HERE/d' "${test_file}"
done
}

run_tests() {
local slug
slug="${1}"
"${FACTOR}" -e="USING: vocabs.loader tools.test tools.test.private namespaces kernel system ; \".\" add-vocab-root \"${slug}\" require \"${slug}\" test test-failures get empty? [ 0 exit ] [ :test-failures 1 exit ] if"
}

verify_exercise() {
local dir
local slug
local tmp_dir

dir=$(realpath "${1}")
slug=$(basename "${dir}")
tmp_dir="$(pwd)/test-exercises/${slug}"

echo "Verifying ${slug} exercise..."

rm -rf "${tmp_dir}" 2>/dev/null || true
mkdir -p "${tmp_dir}"
cp -r "${dir}/." "${tmp_dir}"
local rc=0
(
cd "${tmp_dir}"
copy_example_or_examplar_to_solution
unskip_tests
run_tests "${slug}"
) || rc=$?
rm -rf "${tmp_dir}" 2>/dev/null || true
return "${rc}"
}

verify_exercises() {
local exercise_slug

exercise_slug="${1}"

local rc=0
local -a failed=()

shopt -s nullglob
count=0
for exercise_dir in ./exercises/{concept,practice}/${exercise_slug}/; do
if [[ -d "${exercise_dir}" ]]; then
slug=$(basename "${exercise_dir}")
if jq -e --arg s "${slug}" '.exercises.practice[] | select(.slug == $s) | .status == "wip"' config.json > /dev/null 2>&1; then
echo "Skipping ${slug} (wip)..."
((++count))
continue
fi
if ! verify_exercise "${exercise_dir}"; then
rc=1
failed+=("$(basename "${exercise_dir}")")
fi
((++count))
fi
done
((count > 0)) || die 'no matching exercises found!'
if (( ${#failed[@]} > 0 )); then
echo >&2 ""
echo >&2 "FAILED: ${failed[*]}"
fi
return "${rc}"
}

exercise_slug="${1:-*}"
verify_exercises "${exercise_slug}"
8 changes: 4 additions & 4 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@
},
"files": {
"solution": [
"%{kebab_slug}.factor"
"%{kebab_slug}/%{kebab_slug}.factor"
],
"test": [
"%{kebab_slug}-tests.factor"
"%{kebab_slug}/%{kebab_slug}-tests.factor"
],
"example": [
".meta/%{kebab_slug}-example.factor"
".meta/example.factor"
],
"exemplar": [
".meta/%{kebab_slug}-exemplar.factor"
".meta/exemplar.factor"
]
},
"exercises": {
Expand Down
6 changes: 3 additions & 3 deletions exercises/practice/accumulate/.meta/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
],
"files": {
"solution": [
"accumulate.factor"
"accumulate/accumulate.factor"
],
"test": [
"accumulate-tests.factor"
"accumulate/accumulate-tests.factor"
],
"example": [
".meta/accumulate-example.factor"
".meta/example.factor"
]
},
"blurb": "Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
USING: arrays kernel locals sequences sequences.extras ;
IN: accumulate

:: accum ( seq xform: ( a -- b ) -- newseq )
:: accum ( seq quot: ( x -- y ) -- newseq )
seq >resizable :> seq
seq length seq new-resizable :> newseq

[ seq empty? ] [
seq pop xform call
seq pop quot call
newseq push
] until

newseq reverse >array ; inline

6 changes: 2 additions & 4 deletions exercises/practice/accumulate/.meta/generator.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
module Accumulate

const HEADER = "USING: accumulate math unicode tools.test ;\nEXCLUDE: sequences => accumulate ;"

const ACCUMULATORS = Dict(
"(x) => x * x" => "[ dup * ]",
"(x) => upcase(x)" => "[ >upper ]",
"(x) => reverse(x)" => "[ reverse ]",
"(x) => reverse(x)" => "[ string-reverse ]",
"""(x) => accumulate(["1", "2", "3"], (y) => x + y)""" => """[ [ swap append ] curry { "1" "2" "3" } swap accumulate ]""",
)

Expand Down Expand Up @@ -40,7 +38,7 @@ function gen_test_case(case)
list = format_input_list(case["input"]["list"])
acc = ACCUMULATORS[case["input"]["accumulator"]]
expected = format_value(case["expected"])
return "{ $(expected) } [ $(list) $(acc) accumulate ] unit-test"
return "{ $(expected) }\n[ $(list) $(acc) accum ] unit-test"
end

end
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
USING: accumulate io kernel math sequences tools.test unicode ;
USING: accumulate io kernel lexer tools.test unicode ;
IN: accumulate.tests

: STOP-HERE ( -- ) lexer get [ text>> length ] keep line<< ; parsing

"Accumulate:" print

"accumulate empty" print
{ { } } [ { } [ sq ] accum ] unit-test
{ { } }
[ { } [ dup * ] accum ] unit-test

STOP-HERE

"accumulate squares" print
{ { 1 4 9 } } [ { 1 2 3 } [ sq ] accum ] unit-test
{ { 1 4 9 } }
[ { 1 2 3 } [ dup * ] accum ] unit-test

"accumulate upcases" print
{ { "HELLO" "WORLD" } }
Expand All @@ -16,8 +22,3 @@ IN: accumulate.tests
"accumulate reversed strings" print
{ { "eht" "kciuq" "nworb" "xof" "cte" } }
[ { "the" "quick" "brown" "fox" "etc" } [ string-reverse ] accum ] unit-test

"accumulate recursively" print
{ { { "a1" "a2" "a3" } { "b1" "b2" "b3" } { "c1" "c2" "c3" } } }
[ { "a" "b" "c" } [ dup dup { "1" "2" "3" } [ append ] accum ] accum ] unit-test

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
USING: kernel ;
IN: accumulate

: accum ( seq xform: ( a -- b ) -- newseq )
:: accum ( seq quot: ( x -- y ) -- newseq )
"unimplemented" throw ;

6 changes: 3 additions & 3 deletions exercises/practice/acronym/.meta/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
],
"files": {
"solution": [
"acronym.factor"
"acronym/acronym.factor"
],
"test": [
"acronym-tests.factor"
"acronym/acronym-tests.factor"
],
"example": [
".meta/acronym-example.factor"
".meta/example.factor"
]
},
"blurb": "Convert a long phrase to its acronym.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
USING: ascii kernel sequences splitting ;
IN: acronym

: acronym ( str -- abbr )
: abbreviate ( phrase -- acronym )
" -_" split
[ empty? ] reject
[ 1 head ] map
"" join
>upper
;

>upper ;
5 changes: 1 addition & 4 deletions exercises/practice/acronym/.meta/generator.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
module Acronym

const HEADER = "USING: acronym tools.test ;"


function gen_test_case(case)
phrase = escape_factor(case["input"]["phrase"])
expected = case["expected"]
return """{ "$(expected)" } [ "$(phrase)" abbreviate ] unit-test"""
return """{ "$(expected)" }\n[ "$(phrase)" abbreviate ] unit-test"""
end

end
35 changes: 0 additions & 35 deletions exercises/practice/acronym/acronym-tests.factor

This file was deleted.

44 changes: 44 additions & 0 deletions exercises/practice/acronym/acronym/acronym-tests.factor
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
USING: acronym io kernel lexer tools.test unicode ;
IN: acronym.tests

: STOP-HERE ( -- ) lexer get [ text>> length ] keep line<< ; parsing

"Acronym:" print

"basic" print
{ "PNG" }
[ "Portable Network Graphics" abbreviate ] unit-test

STOP-HERE

"lowercase words" print
{ "ROR" }
[ "Ruby on Rails" abbreviate ] unit-test

"punctuation" print
{ "FIFO" }
[ "First In, First Out" abbreviate ] unit-test

"all caps word" print
{ "GIMP" }
[ "GNU Image Manipulation Program" abbreviate ] unit-test

"punctuation without whitespace" print
{ "CMOS" }
[ "Complementary metal-oxide semiconductor" abbreviate ] unit-test

"very long abbreviation" print
{ "ROTFLSHTMDCOALM" }
[ "Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me" abbreviate ] unit-test

"consecutive delimiters" print
{ "SIMUFTA" }
[ "Something - I made up from thin air" abbreviate ] unit-test

"apostrophes" print
{ "HC" }
[ "Halley's Comet" abbreviate ] unit-test

"underscore emphasis" print
{ "TRNT" }
[ "The Road _Not_ Taken" abbreviate ] unit-test
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
USING: kernel ;
IN: acronym

: acronym ( str -- abbr )
: abbreviate ( phrase -- acronym )
"unimplemented" throw ;

Loading