first draft (#3)

This commit is contained in:
Lars Hvam
2021-11-24 06:48:56 +01:00
committed by GitHub
parent f564fcf38f
commit fbc9e5ac64
24 changed files with 2389 additions and 156 deletions

View File

@@ -1,5 +1,11 @@
.git/ .git/
.appends/
.github/ .github/
.gitattributes
.gitignore
Dockerfile
bin/run-in-docker.sh bin/run-in-docker.sh
bin/run-tests-in-docker.sh bin/run-tests-in-docker.sh
tests/ tests/
output/
open-abap/

View File

@@ -15,6 +15,11 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Use Node.js LTS (16.x)
uses: actions/setup-node@270253e841af726300e85d718a5f606959b2903c
with:
node-version: '16'
- run: npm ci
- run: npm test
- name: Run Tests in Docker - name: Run Tests in Docker
run: bin/run-tests-in-docker.sh run: bin/run-tests-in-docker.sh

7
.gitignore vendored
View File

@@ -1 +1,6 @@
tests/*/results.json test/**/results.json
test/**/abap_transpile.json
node_modules
dist
output
open-abap/

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
ignore-scripts=true

View File

@@ -1,8 +1,9 @@
FROM alpine:3.10 FROM node:lts
# TODO: install packages required to run the tests
# RUN apk add --no-cache jq coreutils
WORKDIR /opt/test-runner WORKDIR /opt/test-runner
COPY . . COPY . .
RUN npm ci
RUN npm run build
RUN npm install @abaplint/transpiler-cli -g
RUN npm install @abaplint/runtime -g
ENTRYPOINT ["/opt/test-runner/bin/run.sh"] ENTRYPOINT ["/opt/test-runner/bin/run.sh"]

View File

@@ -1,23 +1,6 @@
# Exercism Test Runner Template # Exercism ABAP Test Runner
This repository is a [template repository](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-template-repository) for creating [test runners][test-runners] for [Exercism][exercism] tracks. The Docker image to automatically run tests on ABAP solutions submitted to [Exercism].
## Using the Test Runner Template
1. Ensure that your track has not already implemented a test runner. If there is, there will be a `https://github.com/exercism/<track>-test-runner` repository (i.e. if your track's slug is `python`, the test runner repo would be `https://github.com/exercism/python-test-runner`)
2. Follow [GitHub's documentation](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template) for creating a repository from a template repository
- Name your new repository based on your language track's slug (i.e. if your track is for Python, your test runner repo name is `python-test-runner`)
3. Remove this [Exercism Test Runner Template](#exercism-test-runner-template) section from the `README.md` file
4. Build the test runner, conforming to the [Test Runner interface specification](https://github.com/exercism/docs/blob/main/building/tooling/test-runners/interface.md).
- Update the files to match your track's needs. At the very least, you'll need to update `bin/run.sh`, `Dockerfile` and the test solutions in the `tests` directory
- Tip: look for `TODO:` comments to point you towards code that need updating
- Tip: look for `OPTIONAL:` comments to point you towards code that _could_ be useful
Once you're happy with your test runner, [open an issue on the exercism/automated-tests repo](https://github.com/exercism/automated-tests/issues/new?assignees=&labels=&template=new-test-runner.md&title=%5BNew+Test+Runner%5D+) to request an official test runner repository for your track.
# Exercism TRACK_NAME_HERE Test Runner
The Docker image to automatically run tests on TRACK_NAME_HERE solutions submitted to [Exercism].
## Run the test runner ## Run the test runner

View File

@@ -6,26 +6,25 @@
# Arguments: # Arguments:
# $1: exercise slug # $1: exercise slug
# $2: absolute path to solution folder # $2: path to solution folder
# $3: absolute path to output directory # $3: path to output directory
# Output: # Output:
# Writes the test results to a results.json file in the passed-in output directory. # 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/test-runners/interface.md # The test results are formatted according to the specifications at https://github.com/exercism/docs/blob/main/building/tooling/test-runners/interface.md
# Example: # Example:
# ./bin/run-in-docker.sh two-fer /absolute/path/to/two-fer/solution/folder/ /absolute/path/to/output/directory/ # ./bin/run-in-docker.sh two-fer path/to/solution/folder/ path/to/output/directory/
# If any required arguments is missing, print the usage and exit # If any required arguments is missing, print the usage and exit
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then 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/" echo "usage: ./bin/run-in-docker.sh exercise-slug path/to/solution/folder/ path/to/output/directory/"
exit 1 exit 1
fi fi
slug="$1" slug="$1"
input_dir="${2%/}" solution_dir=$(realpath "${2%/}")
output_dir="${3%/}" output_dir=$(realpath "${3%/}")
# Create the output directory if it doesn't exist # Create the output directory if it doesn't exist
mkdir -p "${output_dir}" mkdir -p "${output_dir}"
@@ -35,9 +34,11 @@ docker build --rm -t exercism/test-runner .
# Run the Docker image using the settings mimicking the production environment # Run the Docker image using the settings mimicking the production environment
docker run \ docker run \
--rm \ --rm \
--network none \
--read-only \ --read-only \
--mount type=bind,src="${input_dir}",dst=/solution \ --network none \
--mount type=bind,src="${solution_dir}",dst=/solution \
--mount type=bind,src="${output_dir}",dst=/output \ --mount type=bind,src="${output_dir}",dst=/output \
--mount type=tmpfs,dst=/tmp \ exercism/test-runner "${slug}" "//solution" "//output"
exercism/test-runner "${slug}" /solution /output
# --mount type=tmpfs,dst=/tmp \
# https://stackoverflow.com/questions/48427366/docker-build-command-add-c-program-files-git-to-the-path-passed-as-build-argu

View File

@@ -1,27 +1,3 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# Synopsis: # todo
# Test the test runner Docker image by running it against a predefined set of
# solutions with an expected output.
# The test runner Docker image is built automatically.
# Output:
# Outputs the diff of the expected test results against the actual test results
# generated by the test runner Docker image.
# Example:
# ./bin/run-tests-in-docker.sh
# Build the Docker image
docker build --rm -t exercism/test-runner .
# Run the Docker image using the settings mimicking the production environment
docker run \
--rm \
--network none \
--read-only \
--mount type=bind,src="${PWD}/tests",dst=/opt/test-runner/tests \
--mount type=tmpfs,dst=/tmp \
--workdir /opt/test-runner \
--entrypoint /opt/test-runner/bin/run-tests.sh \
exercism/test-runner

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env sh
# Synopsis:
# Test the test runner by running it against a predefined set of solutions
# with an expected output.
# Output:
# Outputs the diff of the expected test results against the actual test results
# generated by the test runner.
# Example:
# ./bin/run-tests.sh
exit_code=0
# Iterate over all test directories
for test_dir in tests/*; do
test_dir_name=$(basename "${test_dir}")
test_dir_path=$(realpath "${test_dir}")
results_file_path="${test_dir_path}/results.json"
expected_results_file_path="${test_dir_path}/expected_results.json"
bin/run.sh "${test_dir_name}" "${test_dir_path}" "${test_dir_path}"
# OPTIONAL: Normalize the results file
# If the results.json file contains information that changes between
# different test runs (e.g. timing information or paths), you should normalize
# the results file to allow the diff comparison below to work as expected
# sed -i -E \
# -e 's/Elapsed time: [0-9]+\.[0-9]+ seconds//g' \
# -e "s~${test_dir_path}~/solution~g" \
# "${results_file_path}"
echo "${test_dir_name}: comparing results.json to expected_results.json"
diff "${results_file_path}" "${expected_results_file_path}"
if [ $? -ne 0 ]; then
exit_code=1
fi
done
exit ${exit_code}

View File

@@ -5,25 +5,27 @@
# Arguments: # Arguments:
# $1: exercise slug # $1: exercise slug
# $2: absolute path to solution folder # $2: path to solution folder
# $3: absolute path to output directory # $3: path to output directory
# Output: # Output:
# Writes the test results to a results.json file in the passed-in output directory. # 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/test-runners/interface.md # The test results are formatted according to the specifications at https://github.com/exercism/docs/blob/main/building/tooling/test-runners/interface.md
# Example: # Example:
# ./bin/run.sh two-fer /absolute/path/to/two-fer/solution/folder/ /absolute/path/to/output/directory/ # ./bin/run.sh two-fer path/to/solution/folder/ path/to/output/directory/
echo $@
# If any required arguments is missing, print the usage and exit # If any required arguments is missing, print the usage and exit
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
echo "usage: ./bin/run.sh exercise-slug /absolute/path/to/two-fer/solution/folder/ /absolute/path/to/output/directory/" echo "usage: ./bin/run.sh exercise-slug path/to/solution/folder/ path/to/output/directory/"
exit 1 exit 1
fi fi
slug="$1" slug="$1"
input_dir="${2%/}" solution_dir="$2"
output_dir="${3%/}" output_dir="$3"
results_file="${output_dir}/results.json" results_file="${output_dir}/results.json"
# Create the output directory if it doesn't exist # Create the output directory if it doesn't exist
@@ -33,27 +35,7 @@ echo "${slug}: testing..."
# Run the tests for the provided implementation file and redirect stdout and # Run the tests for the provided implementation file and redirect stdout and
# stderr to capture it # stderr to capture it
# TODO: Replace 'RUN_TESTS_COMMAND' with the command to run the tests test_output=$(node dist/src/index.js ${slug} "${solution_dir}" "${output_dir}" "${results_file}" 2>&1)
test_output=$(RUN_TESTS_COMMAND 2>&1) echo ${test_output}
# Write the results.json file based on the exit code of the command that was
# just executed that tested the implementation file
if [ $? -eq 0 ]; then
jq -n '{version: 1, status: "pass"}' > ${results_file}
else
# OPTIONAL: Sanitize the output
# In some cases, the test output might be overly verbose, in which case stripping
# the unneeded information can be very helpful to the student
# sanitized_test_output=$(printf "${test_output}" | sed -n '/Test results:/,$p')
# OPTIONAL: Manually add colors to the output to help scanning the output for errors
# If the test output does not contain colors to help identify failing (or passing)
# tests, it can be helpful to manually add colors to the output
# colorized_test_output=$(echo "${test_output}" \
# | GREP_COLOR='01;31' grep --color=always -E -e '^(ERROR:.*|.*failed)$|$' \
# | GREP_COLOR='01;32' grep --color=always -E -e '^.*passed$|$')
jq -n --arg output "${test_output}" '{version: 1, status: "fail", message: $output}' > ${results_file}
fi
echo "${slug}: done" echo "${slug}: done"

2075
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "@exercism/abap-test-runner",
"private": true,
"description": "Automated Test runner for exercism solutions in ABAP.",
"version": "0.1.0",
"license": "AGPL-3.0-or-later",
"repository": {
"type": "git",
"url": "https://github.com/exercism/abap-test-runner"
},
"directories": {
"lib": "./dist",
"doc": "./docs",
"test": "./test"
},
"bin": {
"abap-test-runner": "bin/run.sh"
},
"scripts": {
"build": "tsc --outDir ./dist && git clone --depth=1 https://github.com/open-abap/open-abap || true",
"test": "npm run build && mocha"
},
"mocha": {
"recursive": true,
"timeout": 60000,
"spec": "dist/test/**/*.js",
"require": "source-map-support/register"
},
"dependencies": {
"@abaplint/transpiler-cli": "^1.6.63",
"@abaplint/transpiler": "^1.6.63",
"@abaplint/runtime": "^1.6.63"
},
"devDependencies": {
"@types/chai": "^4.2.22",
"@types/mocha": "^9.0.0",
"chai": "^4.3.4",
"mocha": "^9.1.3",
"source-map-support": "^0.5.20",
"typescript": "^4.4.4",
"@types/node": "^16.11.7"
}
}

69
src/index.ts Normal file
View File

@@ -0,0 +1,69 @@
import * as Transpiler from "@abaplint/transpiler";
import * as fs from "fs";
import * as path from "path";
import { execSync } from 'child_process'
const slug = process.argv[2];
const inputDir = process.argv[3];
const outputDir = process.argv[4];
const outputFile = process.argv[5];
export interface ITranspilerConfig {
input_folder: string;
/** list of regex, case insensitive, empty gives all files, positive list */
input_filter: string[];
output_folder: string;
lib: string;
write_unit_tests: boolean;
write_source_map: boolean;
options: Transpiler.ITranspilerOptions;
}
function run() {
execSync(`cp ${inputDir}/* ${outputDir}`, {
stdio: 'pipe',
});
let config: ITranspilerConfig = {
input_folder: outputDir,
input_filter: [],
output_folder: outputDir,
lib: "",
write_source_map: true,
write_unit_tests: true,
options: {
ignoreSyntaxCheck: false,
addFilenames: true,
addCommonJS: true,
unknownTypes: "runtimeError",
}
}
console.dir(outputDir);
fs.writeFileSync(outputDir + "/abap_transpile.json", JSON.stringify(config, null, 2));
execSync(`cp open-abap/src/unit/*.clas.abap ${outputDir}`, {
stdio: 'pipe',
});
const resCompile = execSync(`npx abap_transpile > ${outputDir}/_compile.txt`, {
stdio: 'pipe',
cwd: outputDir
});
console.dir(resCompile.toString());
execSync(`npm link @abaplint/runtime`, {
stdio: 'pipe',
cwd: outputDir
});
const resRun = execSync(`node index.mjs > foobar.txt`, {
stdio: 'pipe',
cwd: outputDir
});
// console.dir(resRun.toString());
fs.writeFileSync(outputFile, `{"version": 1, "status": "pass"}`);
}
run();

View File

@@ -0,0 +1,12 @@
CLASS zcl_hello_world DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_hello_world IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
out->write( 'Hello, World' ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,30 @@
CLASS ltcl_hello_world DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT FINAL.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun_out.
PRIVATE SECTION.
METHODS test FOR TESTING RAISING cx_static_check.
DATA text TYPE string.
ENDCLASS.
CLASS ltcl_hello_world IMPLEMENTATION.
METHOD test.
CAST if_oo_adt_classrun( NEW zcl_hello_world( ) )->main( me ).
cl_abap_unit_assert=>assert_equals(
act = if_oo_adt_classrun_out~get( )
exp = 'Hello, World' ).
ENDMETHOD.
METHOD if_oo_adt_classrun_out~write.
text = data.
ENDMETHOD.
METHOD if_oo_adt_classrun_out~get.
output = text.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,12 @@
CLASS zcl_simple DEFINITION PUBLIC.
PUBLIC SECTION.
METHODS run RETURNING VALUE(res) TYPE i.
ENDCLASS.
CLASS zcl_simple IMPLEMENTATION.
METHOD run.
res = 3.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,18 @@
CLASS ltcl_simple DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT FINAL.
PRIVATE SECTION.
METHODS test FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltcl_simple IMPLEMENTATION.
METHOD test.
DATA simple TYPE REF TO zcl_simple.
CREATE OBJECT simple.
cl_abap_unit_assert=>assert_equals(
act = simple->run( )
exp = 3 ).
ENDMETHOD.
ENDCLASS.

16
test/test.ts Normal file
View File

@@ -0,0 +1,16 @@
import { spawnSync } from 'child_process'
import { join, resolve } from 'path'
const root = resolve(__dirname, '../..');
const fixtures = resolve(root, 'test', 'fixtures');
const bin = resolve(root, 'bin');
const run = resolve(bin, 'run.sh');
describe('abap-test-runner', async () => {
it('simple, pass', async () => {
const slug = "simple";
const path = join(fixtures, slug, 'pass');
const output = join(root, 'output');
const res = spawnSync('bash', [run, slug, path, output], {cwd: root});
});
});

View File

@@ -1,5 +0,0 @@
{
"version": 1,
"status": "fail",
"message": "TODO: replace with correct output"
}

View File

@@ -1,5 +0,0 @@
{
"version": 1,
"status": "fail",
"message": "TODO: replace with correct output"
}

View File

@@ -1,5 +0,0 @@
{
"version": 1,
"status": "fail",
"message": "TODO: replace with correct output"
}

View File

@@ -1,4 +0,0 @@
{
"version": 1,
"status": "pass"
}

View File

@@ -1,5 +0,0 @@
{
"version": 1,
"status": "fail",
"message": "TODO: replace with correct output"
}

64
tsconfig.json Normal file
View File

@@ -0,0 +1,64 @@
{
"compilerOptions": {
/* Basic Options */
"target": "ESNEXT" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"lib": [
"esnext"
] /* Specify library files to be included in the compilation. */,
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist" /* Redirect output structure to the directory. */,
"rootDirs": [
"./src", "./test"
] /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true /* Report errors on unused parameters. */,
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
"baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
// "paths": { "~src/*": ["./src/*"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}
}