Skip to content

Commit

Permalink
Merge branch 'develop' into chore/lti/extend-content-selection-table
Browse files Browse the repository at this point in the history
  • Loading branch information
raffifasaro authored Jan 30, 2025
2 parents 19e3dd7 + 7066793 commit 7d71afc
Show file tree
Hide file tree
Showing 95 changed files with 1,407 additions and 536 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:
name: Coverage Report Server Tests
path: build/reports/jacoco/test/html
- name: Append Per-Module Coverage to Job Summary
if: success() || failure()
if: success()
run: |
AGGREGATED_REPORT_FILE=./module_coverage_report.md
python3 ./supporting_scripts/code-coverage/per_module_cov_report/parse_module_coverage.py build/reports/jacoco $AGGREGATED_REPORT_FILE
Expand Down
8 changes: 8 additions & 0 deletions docs/user/exercises/programming-exercise-features.inc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ Instructors can still use those templates to generate programming exercises and
+----------------------+----------+---------+
| Bash | yes | yes |
+----------------------+----------+---------+
| MATLAB | yes | no |
+----------------------+----------+---------+
| Ruby | 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 @@ -95,6 +99,10 @@ Instructors can still use those templates to generate programming exercises and
+----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+
| Bash | no | no | no | no | n/a | no | L: yes, J: no |
+----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+
| MATLAB | no | no | no | no | n/a | no | L: yes, J: no |
+----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+
| Ruby | 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.
Expand Down
2 changes: 1 addition & 1 deletion gradle/jacoco.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ ext {
? ModuleCoverageThresholds.collect {element -> element.key}
: includedModules as ArrayList

// we want to ignore some generated files in the domain folders
ignoredDirectories = [
"**/$BasePath/**/domain/**/*_*",
"**/$BasePath/core/config/migration/entries/**",
"**/org/gradle/**",
"**/gradle-wrapper.jar/**"
]
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/de/tum/cit/aet/artemis/ArtemisApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
import org.springframework.boot.info.GitProperties;
import org.springframework.core.env.Environment;

import de.tum.cit.aet.artemis.core.config.LicenseConfiguration;
import de.tum.cit.aet.artemis.core.config.ProgrammingLanguageConfiguration;
import de.tum.cit.aet.artemis.core.config.TheiaConfiguration;
import tech.jhipster.config.DefaultProfileUtil;
import tech.jhipster.config.JHipsterConstants;

@SpringBootApplication
@EnableConfigurationProperties({ LiquibaseProperties.class, ProgrammingLanguageConfiguration.class, TheiaConfiguration.class })
@EnableConfigurationProperties({ LiquibaseProperties.class, ProgrammingLanguageConfiguration.class, TheiaConfiguration.class, LicenseConfiguration.class })
public class ArtemisApp {

private static final Logger log = LoggerFactory.getLogger(ArtemisApp.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public CreateContainerResponse configureContainer(String containerName, String i
// container from exiting until it finishes.
// It waits until the script that is running the tests (see below execCreateCmdResponse) is completed, and until the result files are extracted which is indicated
// by the creation of a file "stop_container.txt" in the container's root directory.
.withCmd("sh", "-c", "while [ ! -f " + LOCALCI_WORKING_DIRECTORY + "/stop_container.txt ]; do sleep 0.5; done")
.withEntrypoint().withCmd("sh", "-c", "while [ ! -f " + LOCALCI_WORKING_DIRECTORY + "/stop_container.txt ]; do sleep 0.5; done")
// .withCmd("tail", "-f", "/dev/null") // Activate for debugging purposes instead of the above command to get a running container that you can peek into using
// "docker exec -it <container-id> /bin/bash".
.exec();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.tum.cit.aet.artemis.core.config;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import jakarta.annotation.Nullable;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Profile;

@Profile(PROFILE_CORE)
@ConfigurationProperties(prefix = "artemis.licenses")
public class LicenseConfiguration {

private final MatLabLicense matlab;

public record MatLabLicense(String licenseServer) {
}

public LicenseConfiguration(MatLabLicense matlab) {
this.matlab = matlab;
}

@Nullable
public String getMatlabLicenseServer() {
if (matlab == null) {
return null;
}
return matlab.licenseServer();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ public enum ProgrammingLanguage {
JAVA,
JAVASCRIPT,
KOTLIN,
MATLAB,
OCAML,
PYTHON,
R,
RUBY,
RUST,
SWIFT,
TYPESCRIPT,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package de.tum.cit.aet.artemis.programming.service;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.Map;
import java.util.Objects;

import jakarta.annotation.Nullable;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.core.config.LicenseConfiguration;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage;
import de.tum.cit.aet.artemis.programming.domain.ProjectType;

/**
* Provides licensing information for proprietary software to build jobs via environment variables.
*/
@Profile(PROFILE_CORE)
@Service
public class LicenseService {

private final LicenseConfiguration licenseConfiguration;

public LicenseService(LicenseConfiguration licenseConfiguration) {
this.licenseConfiguration = licenseConfiguration;
}

/**
* Checks whether a required license is configured for the specified exercise type.
* If no license is required this returns true.
*
* @param programmingLanguage the programming language of the exercise type
* @param projectType the project type of the exercise type
* @return whether a required license is configured
*/
public boolean isLicensed(ProgrammingLanguage programmingLanguage, @Nullable ProjectType projectType) {
if (programmingLanguage == ProgrammingLanguage.MATLAB && projectType == null) {
return licenseConfiguration.getMatlabLicenseServer() != null;
}

return true;
}

/**
* Returns environment variables required to run programming exercise tests.
*
* @param programmingLanguage the programming language of the exercise
* @param projectType the project type of the exercise
* @return environment variables for the specified exercise type
*/
public Map<String, String> getEnvironment(ProgrammingLanguage programmingLanguage, @Nullable ProjectType projectType) {
if (programmingLanguage == ProgrammingLanguage.MATLAB && projectType == null) {
return Map.of("MLM_LICENSE_FILE", Objects.requireNonNull(licenseConfiguration.getMatlabLicenseServer()));
}

return Map.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand All @@ -17,7 +18,10 @@

import de.tum.cit.aet.artemis.buildagent.dto.DockerFlagsDTO;
import de.tum.cit.aet.artemis.buildagent.dto.DockerRunConfig;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseBuildConfig;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage;
import de.tum.cit.aet.artemis.programming.domain.ProjectType;

@Profile(PROFILE_CORE)
@Service
Expand All @@ -27,6 +31,12 @@ public class ProgrammingExerciseBuildConfigService {

private final ObjectMapper objectMapper = new ObjectMapper();

private final LicenseService licenseService;

public ProgrammingExerciseBuildConfigService(LicenseService licenseService) {
this.licenseService = licenseService;
}

/**
* Converts a JSON string representing Docker flags (in JSON format)
* into a {@link DockerRunConfig} instance.
Expand All @@ -46,25 +56,60 @@ public class ProgrammingExerciseBuildConfigService {
public DockerRunConfig getDockerRunConfig(ProgrammingExerciseBuildConfig buildConfig) {
DockerFlagsDTO dockerFlagsDTO = parseDockerFlags(buildConfig);

return getDockerRunConfigFromParsedFlags(dockerFlagsDTO);
String network;
Map<String, String> exerciseEnvironment;
if (dockerFlagsDTO != null) {
network = dockerFlagsDTO.network();
exerciseEnvironment = dockerFlagsDTO.env();
}
else {
network = null;
exerciseEnvironment = null;
}

ProgrammingExercise exercise = buildConfig.getProgrammingExercise();
if (exercise == null) {
return createDockerRunConfig(network, exerciseEnvironment);
}

ProgrammingLanguage programmingLanguage = exercise.getProgrammingLanguage();
ProjectType projectType = exercise.getProjectType();
Map<String, String> environment = addLanguageSpecificEnvironment(exerciseEnvironment, programmingLanguage, projectType);

return createDockerRunConfig(network, environment);
}

@Nullable
private Map<String, String> addLanguageSpecificEnvironment(@Nullable Map<String, String> exerciseEnvironment, ProgrammingLanguage language, ProjectType projectType) {
Map<String, String> licenseEnvironment = licenseService.getEnvironment(language, projectType);
if (licenseEnvironment.isEmpty()) {
return exerciseEnvironment;
}

Map<String, String> env = new HashMap<>(licenseEnvironment);
if (exerciseEnvironment != null) {
env.putAll(exerciseEnvironment);
}

return env;
}

DockerRunConfig getDockerRunConfigFromParsedFlags(DockerFlagsDTO dockerFlagsDTO) {
if (dockerFlagsDTO == null) {
DockerRunConfig createDockerRunConfig(String network, Map<String, String> environmentMap) {
if (network == null && environmentMap == null) {
return null;
}
List<String> env = new ArrayList<>();
boolean isNetworkDisabled = dockerFlagsDTO.network() != null && dockerFlagsDTO.network().equals("none");
List<String> environmentStrings = new ArrayList<>();
boolean isNetworkDisabled = network != null && network.equals("none");

if (dockerFlagsDTO.env() != null) {
for (Map.Entry<String, String> entry : dockerFlagsDTO.env().entrySet()) {
if (environmentMap != null) {
for (Map.Entry<String, String> entry : environmentMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
env.add(key + "=" + value);
environmentStrings.add(key + "=" + value);
}
}

return new DockerRunConfig(isNetworkDisabled, env);
return new DockerRunConfig(isNetworkDisabled, environmentStrings);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,7 @@ public void validateDockerFlags(ProgrammingExercise programmingExercise) {
}
}

DockerRunConfig dockerRunConfig = programmingExerciseBuildConfigService.getDockerRunConfigFromParsedFlags(dockerFlagsDTO);
DockerRunConfig dockerRunConfig = programmingExerciseBuildConfigService.createDockerRunConfig(dockerFlagsDTO.network(), dockerFlagsDTO.env());

if (List.of(ProgrammingLanguage.SWIFT, ProgrammingLanguage.HASKELL).contains(programmingExercise.getProgrammingLanguage()) && dockerRunConfig.isNetworkDisabled()) {
throw new BadRequestAlertException("This programming language does not support disabling the network access feature", "Exercise", "networkAccessNotSupported");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package de.tum.cit.aet.artemis.programming.service;

import java.util.HashMap;
import java.util.EnumMap;
import java.util.Map;

import jakarta.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;

import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage;
import de.tum.cit.aet.artemis.programming.domain.ProjectType;

/**
* This service provides information about features the different ProgrammingLanguages support.
Expand All @@ -18,7 +21,16 @@ public abstract class ProgrammingLanguageFeatureService implements InfoContribut

private static final Logger log = LoggerFactory.getLogger(ProgrammingLanguageFeatureService.class);

protected final Map<ProgrammingLanguage, ProgrammingLanguageFeature> programmingLanguageFeatures = new HashMap<>();
private final LicenseService licenseService;

private final Map<ProgrammingLanguage, ProgrammingLanguageFeature> programmingLanguageFeatures;

protected ProgrammingLanguageFeatureService(LicenseService licenseService) {
this.licenseService = licenseService;
this.programmingLanguageFeatures = getEnabledFeatures();
}

protected abstract Map<ProgrammingLanguage, ProgrammingLanguageFeature> getSupportedProgrammingLanguageFeatures();

/**
* Get the ProgrammingLanguageFeature configured for the given ProgrammingLanguage.
Expand All @@ -37,12 +49,44 @@ public ProgrammingLanguageFeature getProgrammingLanguageFeatures(ProgrammingLang
return programmingLanguageFeature;
}

public Map<ProgrammingLanguage, ProgrammingLanguageFeature> getProgrammingLanguageFeatures() {
return programmingLanguageFeatures;
}

@Override
public void contribute(Info.Builder builder) {
builder.withDetail("programmingLanguageFeatures", getProgrammingLanguageFeatures().values());
builder.withDetail("programmingLanguageFeatures", programmingLanguageFeatures.values());
}

private Map<ProgrammingLanguage, ProgrammingLanguageFeature> getEnabledFeatures() {
var features = new EnumMap<ProgrammingLanguage, ProgrammingLanguageFeature>(ProgrammingLanguage.class);
for (var programmingLanguageFeatureEntry : getSupportedProgrammingLanguageFeatures().entrySet()) {
var language = programmingLanguageFeatureEntry.getKey();
var feature = programmingLanguageFeatureEntry.getValue();
if (feature.projectTypes().isEmpty()) {
if (isProjectTypeUsable(language, null)) {
features.put(language, feature);
}
}
else {
var filteredProjectTypes = feature.projectTypes().stream().filter((projectType -> isProjectTypeUsable(language, projectType))).toList();
if (!filteredProjectTypes.isEmpty()) {
// @formatter:off
var filteredFeature = new ProgrammingLanguageFeature(
feature.programmingLanguage(),
feature.sequentialTestRuns(),
feature.staticCodeAnalysis(),
feature.plagiarismCheckSupported(),
feature.packageNameRequired(),
feature.checkoutSolutionRepositoryAllowed(),
filteredProjectTypes,
feature.auxiliaryRepositoriesSupported()
);
// @formatter:on
features.put(language, filteredFeature);
}
}
}
return features;
}

private boolean isProjectTypeUsable(ProgrammingLanguage programmingLanguage, @Nullable ProjectType projectType) {
return licenseService.isLicensed(programmingLanguage, projectType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +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, BASH ->
case KOTLIN, PYTHON, C, HASKELL, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH, MATLAB, RUBY ->
defaultRepositoryUpgradeService;
case SQL, MATLAB, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + programmingLanguage);
case SQL, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + programmingLanguage);
};
}
}
Loading

0 comments on commit 7d71afc

Please sign in to comment.