Skip to content

Commit

Permalink
Add smoke tests (#56)
Browse files Browse the repository at this point in the history
* Remove unused results.json files

* Add scripts to run and run tests in Docker

* Update CI to run smoke tests

* Update dockerignore

* Update expected values
  • Loading branch information
ErikSchierboom authored Oct 27, 2023
1 parent 62caf46 commit 07b38c0
Show file tree
Hide file tree
Showing 25 changed files with 263 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ node_modules
.gitattributes
.dockerignore
Dockerfile
bin/*
!bin/run.sh
test/
26 changes: 25 additions & 1 deletion .github/workflows/ci.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Lint code
run: yarn lint

ci:
unit_tests:
runs-on: ubuntu-latest

strategy:
Expand All @@ -40,3 +40,27 @@ jobs:

- name: Install project dependencies and run tests
run: yarn install --frozen-lockfile

smoke_tests:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1
with:
install: true

- name: Build Docker image and store in cache
uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825
with:
context: .
push: false
load: true
tags: exercism/javascript-representer
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run Tests in Docker
run: bin/run-tests-in-docker.sh
26 changes: 25 additions & 1 deletion .github/workflows/pr.ci.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Lint code
run: yarn lint

ci:
unit_tests:
runs-on: ubuntu-latest

strategy:
Expand All @@ -42,3 +42,27 @@ jobs:

- name: Install project dependencies and run tests
run: yarn install --frozen-lockfile

smoke_tests:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1
with:
install: true

- name: Build Docker image and store in cache
uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825
with:
context: .
push: false
load: true
tags: exercism/javascript-representer
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run Tests in Docker
run: bin/run-tests-in-docker.sh
43 changes: 43 additions & 0 deletions bin/run-in-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env sh

# Synopsis:
# Run the representer on a solution using the representer Docker image.
# The representer Docker image is built automatically.

# Arguments:
# $1: exercise slug
# $2: absolute path to solution folder
# $3: absolute path to output directory

# Output:
# Writes the test results to a results.json file in the passed-in output directory.
# The test results are formatted according to the specifications at https://github.com/exercism/docs/blob/main/building/tooling/representers/interface.md

# Example:
# ./bin/run-in-docker.sh two-fer /absolute/path/to/two-fer/solution/folder/ /absolute/path/to/output/directory/

# If any required arguments is missing, print the usage and exit
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
echo "usage: ./bin/run-in-docker.sh exercise-slug /absolute/path/to/solution/folder/ /absolute/path/to/output/directory/"
exit 1
fi

slug="$1"
input_dir="${2%/}"
output_dir="${3%/}"

# Create the output directory if it doesn't exist
mkdir -p "${output_dir}"

# Build the Docker image
docker build --rm -t exercism/javascript-representer .

# Run the Docker image using the settings mimicking the production environment
docker run \
--rm \
--network none \
--read-only \
--mount type=bind,source="${input_dir}",destination=/solution \
--mount type=bind,source="${output_dir}",destination=/output \
--mount type=tmpfs,destination=/tmp \
exercism/javascript-representer "${slug}" /solution /output
28 changes: 28 additions & 0 deletions bin/run-tests-in-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env sh

# Synopsis:
# Test the representer Docker image by running it against a predefined set of
# solutions with an expected output.
# The representer Docker image is built automatically.

# Output:
# Outputs the diff of the expected representation and mapping against the
# actual representation and mapping generated by the representer.

# Example:
# ./bin/run-tests-in-docker.sh

# Build the Docker image
docker build --rm -t exercism/javascript-representer .

# Run the Docker image using the settings mimicking the production environment
docker run \
--rm \
--network none \
--read-only \
--mount type=bind,source="${PWD}/test",destination=/opt/representer/test \
--mount type=tmpfs,destination=/tmp \
--volume "${PWD}/bin/run-tests.sh:/opt/representer/bin/run-tests.sh" \
--workdir /opt/representer \
--entrypoint /opt/representer/bin/run-tests.sh \
exercism/javascript-representer
44 changes: 44 additions & 0 deletions bin/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env sh

# Synopsis:
# Test the representer by running it against a predefined set of solutions
# with an expected output.

# Output:
# Outputs the diff of the expected representation and mapping against the
# actual representation and mapping generated by the representer.

# Example:
# ./bin/run-tests.sh

exit_code=0

# We need to copy the fixtures to a temp directory as the user
# running within the Docker container does not have permissions
# to run the sed command on the fixtures directory
fixtures_dir="test/fixtures"
tmp_fixtures_dir="/tmp/test/fixtures"
rm -rf "${tmp_fixtures_dir}"
mkdir -p "${tmp_fixtures_dir}"
cp -R ${fixtures_dir}/* "${tmp_fixtures_dir}"

# Iterate over all test directories
for test_file in $(find "${tmp_fixtures_dir}" -name '*.spec.js'); do
slug=$(echo "${test_file:${#tmp_fixtures_dir}+1}" | cut -d / -f 1)
test_dir=$(dirname "${test_file}")
test_dir_name=$(basename "${test_dir}")
test_dir_path=$(realpath "${test_dir}")

bin/run.sh "${slug}" "${test_dir_path}" "${test_dir_path}"

for file in representation.txt mapping.json; do
expected_file="expected_${file}"
echo "${test_dir_name}: comparing ${file} to ${expected_file}"

if ! diff "${test_dir_path}/${file}" "${test_dir_path}/${expected_file}"; then
exit_code=1
fi
done
done

exit ${exit_code}
16 changes: 16 additions & 0 deletions test/fixtures/clock/pass/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"IDENTIFIER_0": "MINUTES_PER_HOUR",
"IDENTIFIER_1": "HOURS_ON_THE_CLOCK",
"IDENTIFIER_2": "MINUTES_PER_CLOCK",
"IDENTIFIER_3": "normalize",
"IDENTIFIER_4": "minutes",
"IDENTIFIER_5": "console",
"IDENTIFIER_6": "log",
"IDENTIFIER_8": "number",
"IDENTIFIER_9": "padStart",
"IDENTIFIER_11": "hours",
"IDENTIFIER_12": "plus",
"IDENTIFIER_13": "minus",
"IDENTIFIER_14": "equals",
"IDENTIFIER_15": "other"
}
35 changes: 35 additions & 0 deletions test/fixtures/clock/pass/expected_representation.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const IDENTIFIER_0 = 60;
const IDENTIFIER_1 = 24;
const IDENTIFIER_2 = IDENTIFIER_0 * IDENTIFIER_1;
function IDENTIFIER_3(IDENTIFIER_4) {
while (IDENTIFIER_4 < 0) {
IDENTIFIER_5.IDENTIFIER_6("minutes < 0", IDENTIFIER_4);
IDENTIFIER_4 += IDENTIFIER_2;
}
return IDENTIFIER_4 % IDENTIFIER_2;
}
export const clockPad = IDENTIFIER_8 => {
return String(IDENTIFIER_8).IDENTIFIER_9(2, '0');
};
export class Clock {
constructor(IDENTIFIER_11, IDENTIFIER_4 = 0) {
this.IDENTIFIER_4 = IDENTIFIER_3(IDENTIFIER_11 * IDENTIFIER_0 + IDENTIFIER_4);
}
toString() {
const IDENTIFIER_11 = Math.floor(this.IDENTIFIER_4 / IDENTIFIER_0);
const IDENTIFIER_4 = this.IDENTIFIER_4 % IDENTIFIER_0;
return `${clockPad(IDENTIFIER_11)}:${clockPad(IDENTIFIER_4)}`;
}
IDENTIFIER_12(IDENTIFIER_4) {
return new Clock(0, this.IDENTIFIER_4 + IDENTIFIER_4);
}
IDENTIFIER_13(IDENTIFIER_4) {
return this.IDENTIFIER_12(-IDENTIFIER_4);
}
valueOf() {
return this.IDENTIFIER_4;
}
IDENTIFIER_14(IDENTIFIER_15) {
return +IDENTIFIER_15 === +this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"IDENTIFIER_1": "name"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const twoFer = (IDENTIFIER_1 = 'you') => `One for ${IDENTIFIER_1}, one for me.`;
1 change: 1 addition & 0 deletions test/fixtures/general/normalize-whitespace/two-fer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const twoFer = (name = 'you') => `One for ${name}, one for me.`
3 changes: 3 additions & 0 deletions test/fixtures/general/remove-comments/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"IDENTIFIER_1": "name"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const twoFer = (IDENTIFIER_1 = 'you') => `One for ${IDENTIFIER_1}, one for me.`;
1 change: 1 addition & 0 deletions test/fixtures/general/remove-comments/two-fer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const twoFer = (name = 'you') => `One for ${name}, one for me.`
17 changes: 17 additions & 0 deletions test/fixtures/general/remove-comments/two-fer.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { twoFer } from './two-fer';

describe('twoFer()', () => {
test('no name given', () => {
expect(twoFer()).toEqual('One for you, one for me.');
});

test('a name given', () => {
const name = 'Alice';
expect(twoFer(name)).toEqual('One for Alice, one for me.');
});

test('another name given', () => {
const name = 'Bob';
expect(twoFer(name)).toEqual('One for Bob, one for me.');
});
});
1 change: 1 addition & 0 deletions test/fixtures/two-fer/error/empty/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Empty file.
1 change: 0 additions & 1 deletion test/fixtures/two-fer/error/syntax/two-fer.js

This file was deleted.

6 changes: 6 additions & 0 deletions test/fixtures/two-fer/fail/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"IDENTIFIER_1": "n",
"IDENTIFIER_2": "console",
"IDENTIFIER_3": "debug",
"IDENTIFIER_4": "log"
}
6 changes: 6 additions & 0 deletions test/fixtures/two-fer/fail/expected_representation.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const twoFer = IDENTIFIER_1 => {
IDENTIFIER_2.IDENTIFIER_3(IDENTIFIER_1);
return `One for you, one for me.`;
};
IDENTIFIER_2.IDENTIFIER_3("Hello there debug message");
IDENTIFIER_2.IDENTIFIER_4("Ok there log message");
4 changes: 0 additions & 4 deletions test/fixtures/two-fer/fail/results.json

This file was deleted.

3 changes: 3 additions & 0 deletions test/fixtures/two-fer/pass/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"IDENTIFIER_1": "name"
}
1 change: 1 addition & 0 deletions test/fixtures/two-fer/pass/expected_representation.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const twoFer = (IDENTIFIER_1 = 'you') => `One for ${IDENTIFIER_1}, one for me.`;
4 changes: 0 additions & 4 deletions test/fixtures/two-fer/pass/results.json

This file was deleted.

0 comments on commit 07b38c0

Please sign in to comment.