diff --git a/docs/user/exercises/programming-exercise-features.inc b/docs/user/exercises/programming-exercise-features.inc index 1b43ab3cae7b..2c29ff79a1f4 100644 --- a/docs/user/exercises/programming-exercise-features.inc +++ b/docs/user/exercises/programming-exercise-features.inc @@ -47,6 +47,8 @@ Instructors can still use those templates to generate programming exercises and +----------------------+----------+---------+ | Go | yes | yes | +----------------------+----------+---------+ + | Bash | yes | yes | + +----------------------+----------+---------+ - Not all ``templates`` support the same feature set and supported features can also change depending on the continuous integration system setup. Depending on the feature set, some options might not be available during the creation of the programming exercise. @@ -91,6 +93,8 @@ Instructors can still use those templates to generate programming exercises and +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ | Go | no | no | yes | yes | n/a | no | L: yes, J: no | +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Bash | no | no | no | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ - *Sequential Test Runs*: ``Artemis`` can generate a build plan which first executes structural and then behavioral tests. This feature can help students to better concentrate on the immediate challenge at hand. - *Static Code Analysis*: ``Artemis`` can generate a build plan which additionally executes static code analysis tools. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java index fa3aa6d65577..0c4894182e1e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java @@ -39,6 +39,7 @@ public enum ProgrammingLanguage { private static final Set ENABLED_LANGUAGES = Set.of( ASSEMBLER, + BASH, C, C_PLUS_PLUS, C_SHARP, diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java index a60e8be4b050..9dd3249bd1a4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java @@ -32,8 +32,9 @@ public TemplateUpgradePolicyService(JavaTemplateUpgradeService javaRepositoryUpg public TemplateUpgradeService getUpgradeService(ProgrammingLanguage programmingLanguage) { return switch (programmingLanguage) { case JAVA -> javaRepositoryUpgradeService; - case KOTLIN, PYTHON, C, HASKELL, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO -> defaultRepositoryUpgradeService; - case SQL, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + programmingLanguage); + case KOTLIN, PYTHON, C, HASKELL, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH -> + defaultRepositoryUpgradeService; + case SQL, MATLAB, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + programmingLanguage); }; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java index 9fb2ac0b6f56..3f14c1b2a015 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java @@ -219,8 +219,8 @@ enum RepositoryCheckoutPath implements CustomizableCheckoutPath { @Override public String forProgrammingLanguage(ProgrammingLanguage language) { return switch (language) { - case JAVA, PYTHON, C, HASKELL, KOTLIN, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO -> "assignment"; - case SQL, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); + case JAVA, PYTHON, C, HASKELL, KOTLIN, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH -> "assignment"; + case SQL, MATLAB, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); }; } }, @@ -230,8 +230,8 @@ public String forProgrammingLanguage(ProgrammingLanguage language) { public String forProgrammingLanguage(ProgrammingLanguage language) { return switch (language) { case JAVA, PYTHON, HASKELL, KOTLIN, SWIFT, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT -> ""; - case C, VHDL, ASSEMBLER, OCAML, C_SHARP, GO -> "tests"; - case SQL, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); + case C, VHDL, ASSEMBLER, OCAML, C_SHARP, GO, BASH -> "tests"; + case SQL, MATLAB, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); }; } }, diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java index a858a171efd7..41b1254cbb7e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java @@ -1,5 +1,6 @@ package de.tum.cit.aet.artemis.programming.service.jenkins; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.BASH; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.C; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.C_PLUS_PLUS; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.C_SHARP; @@ -38,6 +39,7 @@ public class JenkinsProgrammingLanguageFeatureService extends ProgrammingLanguag public JenkinsProgrammingLanguageFeatureService() { // Must be extended once a new programming language is added programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false)); + programmingLanguageFeatures.put(BASH, new ProgrammingLanguageFeature(BASH, false, false, false, false, false, List.of(), false)); programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false)); programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), false)); programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), false)); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java index c54b617d5546..4975c4ce9a17 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java @@ -185,8 +185,8 @@ private JenkinsXmlConfigBuilder builderFor(ProgrammingLanguage programmingLangua throw new UnsupportedOperationException("Xcode templates are not available for Jenkins."); } return switch (programmingLanguage) { - case JAVA, KOTLIN, PYTHON, C, HASKELL, SWIFT, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO -> jenkinsBuildPlanCreator; - case VHDL, ASSEMBLER, OCAML, SQL, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> + case JAVA, KOTLIN, PYTHON, C, HASKELL, SWIFT, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH -> jenkinsBuildPlanCreator; + case VHDL, ASSEMBLER, OCAML, SQL, MATLAB, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException(programmingLanguage + " templates are not available for Jenkins."); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java index 0c46a202574b..e9c7e13b8659 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java @@ -2,6 +2,7 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_LOCALCI; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.ASSEMBLER; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.BASH; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.C; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.C_PLUS_PLUS; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.C_SHARP; @@ -45,6 +46,7 @@ public LocalCIProgrammingLanguageFeatureService() { // Must be extended once a new programming language is added programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), true)); programmingLanguageFeatures.put(ASSEMBLER, new ProgrammingLanguageFeature(ASSEMBLER, false, false, false, false, false, List.of(), true)); + programmingLanguageFeatures.put(BASH, new ProgrammingLanguageFeature(BASH, false, false, false, false, false, List.of(), true)); programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, true, true, false, false, List.of(FACT, GCC), true)); programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), true)); programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), true)); diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 82611f9ceac2..b4984ea6cf97 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -95,6 +95,8 @@ artemis: default: "ghcr.io/ls1intum/artemis-javascript-docker:v1.0.0" r: default: "ghcr.io/ls1intum/artemis-r-docker:v1.0.0" + bash: + default: "ghcr.io/ls1intum/artemis-bash-docker:v1.0.0" c_plus_plus: default: "ghcr.io/ls1intum/artemis-cpp-docker:v1.0.0" c_sharp: diff --git a/src/main/resources/templates/aeolus/bash/default.sh b/src/main/resources/templates/aeolus/bash/default.sh new file mode 100644 index 000000000000..252faca6aaad --- /dev/null +++ b/src/main/resources/templates/aeolus/bash/default.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -e +export AEOLUS_INITIAL_DIRECTORY=${PWD} +set_permissions () { + echo '⚙️ executing set_permissions' + find "${studentParentWorkingDirectoryName}" -type f -exec chmod +x "{}" + +} + +create_results_directory () { + echo '⚙️ executing create_results_directory' + mkdir results +} + +test () { + echo '⚙️ executing test' + bats --report-formatter junit --output results "${testWorkingDirectory}" || true +} + +main () { + if [[ "${1}" == "aeolus_sourcing" ]]; then + return 0 # just source to use the methods in the subshell, no execution + fi + local _script_name + _script_name=${BASH_SOURCE[0]:-$0} + cd "${AEOLUS_INITIAL_DIRECTORY}" + bash -c "source ${_script_name} aeolus_sourcing; set_permissions" + cd "${AEOLUS_INITIAL_DIRECTORY}" + bash -c "source ${_script_name} aeolus_sourcing; create_results_directory" + cd "${AEOLUS_INITIAL_DIRECTORY}" + bash -c "source ${_script_name} aeolus_sourcing; test" +} + +main "${@}" diff --git a/src/main/resources/templates/aeolus/bash/default.yaml b/src/main/resources/templates/aeolus/bash/default.yaml new file mode 100644 index 000000000000..8852c4ac60e9 --- /dev/null +++ b/src/main/resources/templates/aeolus/bash/default.yaml @@ -0,0 +1,15 @@ +api: v0.0.1 +metadata: + name: "Bash" + id: bash +actions: + - name: set_permissions + script: 'find "${studentParentWorkingDirectoryName}" -type f -exec chmod +x "{}" +' + - name: create_results_directory + script: 'mkdir results' + - name: test + script: 'bats --report-formatter junit --output results "${testWorkingDirectory}" || true' + results: + - name: Bats Test Results + path: "results/*.xml" + type: junit diff --git a/src/main/resources/templates/bash/exercise/script.bash b/src/main/resources/templates/bash/exercise/script.bash new file mode 100644 index 000000000000..1c4b4ceea780 --- /dev/null +++ b/src/main/resources/templates/bash/exercise/script.bash @@ -0,0 +1,14 @@ +# TODO: add shebang + +# TODO: list directory entries + +# TODO: create create_me.txt + +# TODO: delete delete_me.txt + +# TODO: rename rename_me.txt to renamed.txt + +# TODO: replace 2.718 with 3.1415 in numbers.txt + +# TODO: exit with an successful status code +exit 1 diff --git a/src/main/resources/templates/bash/readme b/src/main/resources/templates/bash/readme new file mode 100644 index 000000000000..39a96890d707 --- /dev/null +++ b/src/main/resources/templates/bash/readme @@ -0,0 +1,26 @@ +# Bash Scripting Exercise + +This exercise is designed to help you practice basic Bash scripting skills. +Complete the following tasks by writing a Bash script that performs each action. + +1. [task][Add a Shebang](shebang,shebang_custom_message) +Insert a "shebang" at the top of your script to specify that it should be executed with Bash. +Assume Bash is installed at a standard path. + +2. [task][List Directory Entries](list_dir) +Write a command to list all entries in the current directory, including hidden files, and print the output to the terminal. + +3. [task][Create File](file_creation) +Create a file named `create_me.txt` in the current directory. + +4. [task][Delete a File](file_deletion) +Delete the file named `delete_me.txt` from the current directory. + +5. [task][Rename a File](rename) +Rename the file `rename_me.txt` to `renamed.txt`. + +6. [task][Find and Replace Text](replace) +Replace all occurrences of the number `2.718` with `3.1415` in the file `numbers.txt`. Ensure the changes are saved. + +7. [task][Exit with Success](status_code) +Exit the script with a successful status code. diff --git a/src/main/resources/templates/bash/solution/script.bash b/src/main/resources/templates/bash/solution/script.bash new file mode 100644 index 000000000000..aaa638f3dd4f --- /dev/null +++ b/src/main/resources/templates/bash/solution/script.bash @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +ls -a + +touch create_me.txt + +rm delete_me.txt + +mv rename_me.txt renamed.txt + +sed -i 's/2\.718/3.1415/g' numbers.txt + +exit 0 diff --git a/src/main/resources/templates/bash/test/test.bats b/src/main/resources/templates/bash/test/test.bats new file mode 100644 index 000000000000..272af881969f --- /dev/null +++ b/src/main/resources/templates/bash/test/test.bats @@ -0,0 +1,72 @@ +setup_file() { + BATS_TEST_TIMEOUT=10 +} + +setup() { + load "test_helper/common-setup" + _common_setup + + TEST_DATA="$BATS_TEST_DIRNAME/test_data" + + cp "$TEST_DATA"/{numbers.txt,rename_me.txt} "$BATS_TEST_TMPDIR" + + cd "$BATS_TEST_TMPDIR" + touch delete_me.txt + touch .hidden +} + +@test "shebang" { + first_line=$(head -n 1 "$ASSIGNMENT_ROOT/script.bash") + assert_regex "$first_line" '^#!(/usr)?/bin/(env )?bash$' +} + +@test "shebang_custom_message" { + first_line=$(head -n 1 "$ASSIGNMENT_ROOT/script.bash") + + if ! assert_regex "$first_line" '^#!(/usr)?/bin/(env )?bash$' 2>/dev/null; then + echo "$first_line" \ + | batslib_decorate "first line is not a valid shebang" \ + | fail + fi +} + +@test "list_dir" { + run script.bash + + assert_output --partial delete_me.txt + assert_output --partial numbers.txt + assert_output --partial rename_me.txt + assert_output --partial .hidden +} + +@test "file_creation" { + run script.bash + + assert_file_exists create_me.txt +} + +@test "file_deletion" { + run script.bash + + assert_file_not_exists delete_me.txt +} + +@test "rename" { + run script.bash + + assert_file_not_exists rename_me.txt + assert_file_exists renamed.txt + _assert_file_contents renamed.txt "$TEST_DATA/rename_me.txt" +} + +@test "replace" { + run script.bash + + _assert_file_contents numbers.txt "$TEST_DATA/numbers_expected.txt" +} + +@test "status_code" { + run script.bash + + assert_success +} diff --git a/src/main/resources/templates/bash/test/test_data/numbers.txt b/src/main/resources/templates/bash/test/test_data/numbers.txt new file mode 100644 index 000000000000..a252bd33309e --- /dev/null +++ b/src/main/resources/templates/bash/test/test_data/numbers.txt @@ -0,0 +1,3 @@ +2.718 +2.718 2.718 2.718 +21718 diff --git a/src/main/resources/templates/bash/test/test_data/numbers_expected.txt b/src/main/resources/templates/bash/test/test_data/numbers_expected.txt new file mode 100644 index 000000000000..44121ba5f026 --- /dev/null +++ b/src/main/resources/templates/bash/test/test_data/numbers_expected.txt @@ -0,0 +1,3 @@ +3.1415 +3.1415 3.1415 3.1415 +21718 diff --git a/src/main/resources/templates/bash/test/test_data/rename_me.txt b/src/main/resources/templates/bash/test/test_data/rename_me.txt new file mode 100644 index 000000000000..0bc73a7a2f76 --- /dev/null +++ b/src/main/resources/templates/bash/test/test_data/rename_me.txt @@ -0,0 +1 @@ +example content diff --git a/src/main/resources/templates/bash/test/test_helper/common-setup.bash b/src/main/resources/templates/bash/test/test_helper/common-setup.bash new file mode 100644 index 000000000000..23bca120a979 --- /dev/null +++ b/src/main/resources/templates/bash/test/test_helper/common-setup.bash @@ -0,0 +1,34 @@ +_common_setup() { + bats_load_library "bats-support" + bats_load_library "bats-assert" + bats_load_library "bats-file" + + PROJECT_ROOT="$( cd "$BATS_TEST_DIRNAME/.." >/dev/null 2>&1 && pwd )" + ASSIGNMENT_ROOT="$PROJECT_ROOT/${studentParentWorkingDirectoryName}" + + PATH="$ASSIGNMENT_ROOT:$PATH" +} + +# _assert_file_contents +# ============ +# +# Fail if the actual and expected file contents differ. +# +# Usage: _assert_file_contents +# +# IO: +# STDERR - unified diff, on failure +# Options: +# The file being compared. +# The file to compare against. +_assert_file_contents() { + if ! diff_output=$(diff -u --label="actual" --label="expected" "$1" "$2" 2>&1); then + echo "$diff_output" \ + | batslib_decorate "$1: file contents differ" \ + | fail + fi +} + +# reduce output +bats_print_stack_trace() { :; } +bats_print_failed_command() { :; } diff --git a/src/main/resources/templates/jenkins/bash/regularRuns/pipeline.groovy b/src/main/resources/templates/jenkins/bash/regularRuns/pipeline.groovy new file mode 100644 index 000000000000..da257affb46e --- /dev/null +++ b/src/main/resources/templates/jenkins/bash/regularRuns/pipeline.groovy @@ -0,0 +1,55 @@ +/* + * This file configures the actual build steps for the automatic grading. + * + * !!! + * For regular exercises, there is no need to make changes to this file. + * Only this base configuration is actively supported by the Artemis maintainers + * and/or your Artemis instance administrators. + * !!! + */ + +dockerImage = '#dockerImage' +dockerFlags = '#dockerArgs' + +/** + * Main function called by Jenkins. + */ +void testRunner() { + docker.image(dockerImage).inside(dockerFlags) { c -> + runTestSteps() + } +} + +private void runTestSteps() { + test() +} + +/** + * Run unit tests + */ +private void test() { + stage('Set Permissions') { + sh 'find ./assignment -type f -exec chmod +x "{}" +' + } + stage('Create Results Directory') { + sh 'mkdir results' + } + stage('Test') { + sh 'bats --report-formatter junit --output results ./tests || true' + } +} + +/** + * Script of the post build tasks aggregating all JUnit files in $WORKSPACE/results. + * + * Called by Jenkins. + */ +void postBuildTasks() { + sh ''' + sed -i 's/[^[:print:]\t]/�/g' $WORKSPACE/results/*.xml || true + ''' +} + +// very important, do not remove +// required so that Jenkins finds the methods defined in this script +return this diff --git a/src/main/webapp/app/entities/programming/programming-exercise.model.ts b/src/main/webapp/app/entities/programming/programming-exercise.model.ts index bd9246f8569e..ece9e6d6989c 100644 --- a/src/main/webapp/app/entities/programming/programming-exercise.model.ts +++ b/src/main/webapp/app/entities/programming/programming-exercise.model.ts @@ -14,6 +14,7 @@ import dayjs from 'dayjs/esm'; export enum ProgrammingLanguage { EMPTY = 'EMPTY', ASSEMBLER = 'ASSEMBLER', + BASH = 'BASH', C = 'C', C_PLUS_PLUS = 'C_PLUS_PLUS', C_SHARP = 'C_SHARP', diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml index f730d6102f9c..869514c89759 100644 --- a/src/test/resources/config/application.yml +++ b/src/test/resources/config/application.yml @@ -72,6 +72,8 @@ artemis: default: "~~invalid~~" r: default: "~~invalid~~" + bash: + default: "~~invalid~~" c_plus_plus: default: "~~invalid~~" c_sharp: