diff --git a/CHANGELOG.md b/CHANGELOG.md index 19769db1..267c6f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Changed +- [Java] Replace `minimal-json` with regular expression ([#301](https://github.com/cucumber/ci-environment/pull/301)) + ### Removed - [Python] Remove support for end-of-life Python 3.8 and 3.9 ([#297](https://github.com/cucumber/ci-environment/pull/297)) diff --git a/java/pom.xml b/java/pom.xml index a37e8aa5..f26ef5fb 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -48,28 +48,28 @@ + - com.eclipsesource.minimal-json - minimal-json - 0.9.5 + org.junit.jupiter + junit-jupiter + test - org.hamcrest - hamcrest - 3.0 + com.fasterxml.jackson.core + jackson-databind test - org.junit.jupiter - junit-jupiter + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 test - com.fasterxml.jackson.core - jackson-databind + com.fasterxml.jackson.module + jackson-module-parameter-names test @@ -109,6 +109,10 @@ org.apache.maven.plugins maven-compiler-plugin + + + true + generate-ci-environments @@ -169,39 +173,6 @@ - - org.apache.maven.plugins - maven-shade-plugin - - - package - - shade - - - - - com.eclipsesource.minimal-json:minimal-json - - - - - com.eclipsesource.json - io.cucumber.cienvironment.internal.com.eclipsesource.json - - - - - com.eclipsesource.minimal-json:minimal-json - - META-INF/MANIFEST.MF - - - - - - - diff --git a/java/src/main/java/io/cucumber/cienvironment/CiEnvironmentImpl.java b/java/src/main/java/io/cucumber/cienvironment/CiEnvironmentImpl.java index c64b2295..b95bb812 100644 --- a/java/src/main/java/io/cucumber/cienvironment/CiEnvironmentImpl.java +++ b/java/src/main/java/io/cucumber/cienvironment/CiEnvironmentImpl.java @@ -12,9 +12,6 @@ final class CiEnvironmentImpl implements CiEnvironment { public String buildNumber; public Git git; - CiEnvironmentImpl() { - } - CiEnvironmentImpl(String name, String url, String buildNumber, Git git) { this.name = requireNonNull(name); this.url = requireNonNull(url); @@ -71,9 +68,6 @@ final static class Git implements CiEnvironment.Git { public String branch; public String tag; - Git() { - } - Git(String remote, String revision, String branch, String tag) { this.remote = requireNonNull(remote); this.revision = requireNonNull(revision); diff --git a/java/src/main/java/io/cucumber/cienvironment/DetectCiEnvironment.java b/java/src/main/java/io/cucumber/cienvironment/DetectCiEnvironment.java index e7ae683e..e62225ac 100644 --- a/java/src/main/java/io/cucumber/cienvironment/DetectCiEnvironment.java +++ b/java/src/main/java/io/cucumber/cienvironment/DetectCiEnvironment.java @@ -1,12 +1,5 @@ package io.cucumber.cienvironment; -import com.eclipsesource.json.Json; -import com.eclipsesource.json.JsonValue; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Optional; @@ -66,37 +59,9 @@ private static CiEnvironmentImpl.Git detectGit(CiEnvironment ci, Map env) { - String revision = evaluateRevisionGithub(env); + String revision = GithubEventParser.evaluateRevisionGithub(env); if (revision != null) return revision; return ci.getGit().map(git -> evaluate(git.getRevision(), env)).orElse(null); } - /* - * Evaluate the current revision on GitHub. - * - * The GITHUB_SHA environment variable doesn't quite work as expected. - * See: - * * https://github.com/cucumber/ci-environment/issues/67 - * * https://github.com/orgs/community/discussions/26325 - * * https://github.com/cucumber/ci-environment/issues/86 - */ - private static String evaluateRevisionGithub(Map env) { - if (!"pull_request".equals(env.get("GITHUB_EVENT_NAME"))) { - return null; - } - if (env.get("GITHUB_EVENT_PATH") == null) { - throw new RuntimeException("GITHUB_EVENT_PATH not set"); - } - try (InputStreamReader is = getGithubEvent(env)) { - JsonValue event = Json.parse(is); - return event.asObject().get("pull_request").asObject().get("head").asObject().get("sha").asString(); - } catch (Exception e) { - throw new RuntimeException("Could not read .pull_request.head.sha from " + env.get("GITHUB_EVENT_PATH"), e); - } - } - - private static InputStreamReader getGithubEvent(Map env) throws FileNotFoundException { - return new InputStreamReader(new FileInputStream(env.get("GITHUB_EVENT_PATH")), StandardCharsets.UTF_8); - } - } diff --git a/java/src/main/java/io/cucumber/cienvironment/GithubEventParser.java b/java/src/main/java/io/cucumber/cienvironment/GithubEventParser.java new file mode 100644 index 00000000..321c9024 --- /dev/null +++ b/java/src/main/java/io/cucumber/cienvironment/GithubEventParser.java @@ -0,0 +1,80 @@ +package io.cucumber.cienvironment; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +final class GithubEventParser { + + /* + * Evaluate the current revision on GitHub. + * + * The GITHUB_SHA environment variable doesn't quite work as expected. + * See: + * * https://github.com/cucumber/ci-environment/issues/67 + * * https://github.com/orgs/community/discussions/26325 + * * https://github.com/cucumber/ci-environment/issues/86 + */ + static String evaluateRevisionGithub(Map env) { + if (!"pull_request".equals(env.get("GITHUB_EVENT_NAME"))) { + return null; + } + if (env.get("GITHUB_EVENT_PATH") == null) { + return null; + } + try { + Path path = Paths.get(env.get("GITHUB_EVENT_PATH")); + String event = String.join(" ", Files.readAllLines(path)); + return parsePullRequestHeadSha(event); + } catch (IOException e) { + return null; + } + } + + private static final Pattern GITHUB_EVENT_PATTERN = Pattern.compile( + // Start of object + "^\\{" + + // Any leading key-value pairs. + ".*?" + + // Pull request key + "\"pull_request\" *: *" + + // Start of pull request value, must be an object + "\\{" + + // Any leading key-value pairs. + ".*?" + + // Head key + "\"head\" *: *" + + // Start of head value, must be an object + "\\{" + + // Any leading key-value pairs. + ".*?" + + // sha key and value, must be a hash-like string + "\"sha\" *: *\"([a-z0-9]+)\"" + + // Any key-value pairs. + ".*" + + // End of head value. + "}" + + // Any trailing key-value pairs. + ".*" + + // End of pull request value + "}" + + // Any trailing key-value pairs. + ".*" + + // End of object + "}$"); + + static String parsePullRequestHeadSha(String eventJson) { + // Parse json using regex. Not ideal but works for the limited input. + Matcher matcher = GITHUB_EVENT_PATTERN.matcher(eventJson.trim()); + if (!matcher.matches()) { + return null; + } + return matcher.group(1); + } + + +} diff --git a/java/src/test/java/io/cucumber/cienvironment/DetectCiEnvironmentTest.java b/java/src/test/java/io/cucumber/cienvironment/DetectCiEnvironmentTest.java index 40359fb7..14326c69 100644 --- a/java/src/test/java/io/cucumber/cienvironment/DetectCiEnvironmentTest.java +++ b/java/src/test/java/io/cucumber/cienvironment/DetectCiEnvironmentTest.java @@ -22,14 +22,13 @@ import java.util.Map; import static io.cucumber.cienvironment.DetectCiEnvironment.detectCiEnvironment; +import static io.cucumber.cienvironment.Jackson.OBJECT_MAPPER; import static java.nio.file.Files.newBufferedReader; import static java.nio.file.Files.newDirectoryStream; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertEquals; class DetectCiEnvironmentTest { - private static final ObjectMapper mapper = new ObjectMapper() - .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); private static List acceptance_tests_pass() throws IOException { List paths = new ArrayList<>(); @@ -78,7 +77,7 @@ public Expectation convert(Object source, ParameterContext context) throws Argum env.put(parts[0], parts[1]); } } - CiEnvironment expected = mapper.readValue(new File(path + ".json"), CiEnvironmentImpl.class); + CiEnvironment expected = OBJECT_MAPPER.readValue(new File(path + ".json"), CiEnvironmentImpl.class); return new Expectation(env, expected); } catch (IOException e) { throw new ArgumentConversionException("Could not load " + source, e); diff --git a/java/src/test/java/io/cucumber/cienvironment/GitHubPullRequestIntegrationTest.java b/java/src/test/java/io/cucumber/cienvironment/GitHubPullRequestIntegrationTest.java index 05a3b2e3..337ae9c1 100644 --- a/java/src/test/java/io/cucumber/cienvironment/GitHubPullRequestIntegrationTest.java +++ b/java/src/test/java/io/cucumber/cienvironment/GitHubPullRequestIntegrationTest.java @@ -1,16 +1,35 @@ package io.cucumber.cienvironment; +import com.fasterxml.jackson.databind.JsonNode; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import java.io.File; +import java.io.IOException; +import java.util.Map; import static io.cucumber.cienvironment.DetectCiEnvironment.detectCiEnvironment; +import static org.junit.jupiter.api.Assertions.assertEquals; class GitHubPullRequestIntegrationTest { + @Test - void detects_the_correct_revision_for_pull_requests() { - if ("pull_request".equals(System.getenv().get("GITHUB_EVENT_NAME"))) { - CiEnvironment ciEnvironment = detectCiEnvironment(System.getenv()).orElseThrow(() -> new RuntimeException("No CI environment detected")); - System.out.println("Manually verify that the revision is correct"); - System.out.println(ciEnvironment); - } + @EnabledIfEnvironmentVariable(named = "GITHUB_EVENT_NAME", matches = "pull_request", disabledReason = "Must be tested by creating a pull request on Github") + void detects_the_correct_revision_for_pull_requests() throws IOException { + Map env = System.getenv(); + assertEquals(parsePullRequestHeadShaWithJackson(env), parseRevisionWithRegularExpression(env)); + } + + private static String parseRevisionWithRegularExpression(Map env) { + return detectCiEnvironment(env) + .orElseThrow(() -> new RuntimeException("No CI environment detected")) + .getGit().map(CiEnvironment.Git::getRevision) + .orElseThrow(() -> new RuntimeException("No Github Event detected")); + } + + private static String parsePullRequestHeadShaWithJackson(Map env) throws IOException { + File file = new File(env.get("GITHUB_EVENT_PATH")); + JsonNode event = Jackson.OBJECT_MAPPER.readTree(file); + return event.get("pull_request").get("head").get("sha").textValue(); } } diff --git a/java/src/test/java/io/cucumber/cienvironment/Jackson.java b/java/src/test/java/io/cucumber/cienvironment/Jackson.java new file mode 100644 index 00000000..61f835af --- /dev/null +++ b/java/src/test/java/io/cucumber/cienvironment/Jackson.java @@ -0,0 +1,32 @@ +package io.cucumber.cienvironment; + +import com.fasterxml.jackson.annotation.JsonCreator.Mode; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.cfg.ConstructorDetector; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT; +import static com.fasterxml.jackson.annotation.JsonInclude.Value.construct; + +final class Jackson { + public static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new ParameterNamesModule(Mode.PROPERTIES)) + .defaultPropertyInclusion(construct(NON_ABSENT, NON_ABSENT)) + .constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED) + .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) + .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + .enable(DeserializationFeature.USE_LONG_FOR_INTS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET) + .build(); + + private Jackson() { + } +} +