Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Programming exercises: Add Bash programming exercise template #10089

Draft
wants to merge 6 commits into
base: develop
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions docs/user/exercises/programming-exercise-features.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -91,6 +93,8 @@ Instructors can still use those templates to generate programming exercises and
+----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+
| Go | no | no | yes | yes | n/a | no | no | L: yes, J: no |
+----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+
| Bash | no | no | no | no | n/a | no | 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public enum ProgrammingLanguage {

private static final Set<ProgrammingLanguage> ENABLED_LANGUAGES = Set.of(
ASSEMBLER,
BASH,
C,
C_PLUS_PLUS,
C_SHARP,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
}
},
Expand All @@ -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);
};
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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, false));
programmingLanguageFeatures.put(BASH, new ProgrammingLanguageFeature(BASH, false, false, false, false, false, List.of(), false, false));
programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false, false));
programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), false, false));
programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), false, false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,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.");
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(), false, true));
programmingLanguageFeatures.put(ASSEMBLER, new ProgrammingLanguageFeature(ASSEMBLER, false, false, false, false, false, List.of(), false, true));
programmingLanguageFeatures.put(BASH, new ProgrammingLanguageFeature(BASH, false, false, false, false, false, List.of(), false, true));
programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, true, true, false, false, List.of(FACT, GCC), false, true));
programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), false, true));
programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), false, true));
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,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:
Expand Down
33 changes: 33 additions & 0 deletions src/main/resources/templates/aeolus/bash/default.sh
Original file line number Diff line number Diff line change
@@ -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 "${@}"
15 changes: 15 additions & 0 deletions src/main/resources/templates/aeolus/bash/default.yaml
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions src/main/resources/templates/bash/exercise/script.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# TODO: add shebang

# TODO: list all files

# 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
9 changes: 9 additions & 0 deletions src/main/resources/templates/bash/readme
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Bash

1. [task][Shebang](shebang,shebang_custom_message)
2. [task][List Files](list_files)
3. [task][Create File](file_creation)
4. [task][Delete File](file_deletion)
5. [task][Rename File](rename)
6. [task][Find and Replace](replace)
7. [task][Status Code](status_code)
13 changes: 13 additions & 0 deletions src/main/resources/templates/bash/solution/script.bash
Original file line number Diff line number Diff line change
@@ -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
72 changes: 72 additions & 0 deletions src/main/resources/templates/bash/test/test.bats
Original file line number Diff line number Diff line change
@@ -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_files" {
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 file.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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
2.718
2.718 2.718 2.718
21718
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
3.1415
3.1415 3.1415 3.1415
21718
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
example content
Original file line number Diff line number Diff line change
@@ -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 <actual_path> <expected_path>
#
# IO:
# STDERR - unified diff, on failure
# Options:
# <actual_path> The file being compared.
# <expected_path> 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() { :; }
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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',
Expand Down
2 changes: 2 additions & 0 deletions src/test/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ artemis:
default: "~~invalid~~"
r:
default: "~~invalid~~"
bash:
default: "~~invalid~~"
c_plus_plus:
default: "~~invalid~~"
c_sharp:
Expand Down
Loading