lines = new ArrayList<>(Arrays.asList(commandOutput.split("\\r?\\n", -1)));
17 |
18 | while (lines.get(0).isEmpty()) {
19 | lines.remove(0);
20 | }
21 |
22 | return lines.stream().collect(Collectors.joining("\n"));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/normalizer/OutputNormalizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.normalizer;
17 |
18 | import org.gradle.exemplar.executor.ExecutionMetadata;
19 |
20 | public interface OutputNormalizer {
21 | /**
22 | * Remove and update output that is unrelated the the sample output.
23 | *
24 | * @param commandOutput raw command output
25 | * @param executionMetadata environment and execution information
26 | * @return normalized output as a String
27 | */
28 | String normalize(final String commandOutput, final ExecutionMetadata executionMetadata);
29 | }
30 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/normalizer/StripTrailingOutputNormalizer.java:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.normalizer;
2 |
3 | import org.gradle.exemplar.executor.ExecutionMetadata;
4 |
5 | import java.util.Arrays;
6 | import java.util.stream.Collectors;
7 |
8 | public class StripTrailingOutputNormalizer implements OutputNormalizer {
9 | @Override
10 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) {
11 | return Arrays.stream(commandOutput.split("\\r?\\n", -1)).map(StripTrailingOutputNormalizer::stripTrailing).collect(Collectors.joining("\n"));
12 | }
13 |
14 | private static String stripTrailing(String self) {
15 | int len = self.length();
16 | int st = 0;
17 | char[] val = self.toCharArray(); /* avoid getfield opcode */
18 |
19 | while ((st < len) && (Character.isSpaceChar(val[len - 1]))) {
20 | len--;
21 | }
22 | return ((len < self.length())) ? self.substring(0, len) : self;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/normalizer/TrailingNewLineOutputNormalizer.java:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.normalizer;
2 |
3 | import org.gradle.exemplar.executor.ExecutionMetadata;
4 |
5 | import java.util.Arrays;
6 | import java.util.stream.Collectors;
7 |
8 | public class TrailingNewLineOutputNormalizer implements OutputNormalizer {
9 | @Override
10 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) {
11 | if (commandOutput.isEmpty()) {
12 | return commandOutput;
13 | }
14 | return Arrays.stream(commandOutput.split("\\r?\\n")).collect(Collectors.joining("\n"));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/normalizer/WorkingDirectoryOutputNormalizer.java:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.normalizer;
2 |
3 | import org.gradle.exemplar.executor.ExecutionMetadata;
4 |
5 | import java.io.IOException;
6 | import java.io.UncheckedIOException;
7 |
8 | public class WorkingDirectoryOutputNormalizer implements OutputNormalizer {
9 | @Override
10 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) {
11 | try {
12 | return commandOutput.replace(executionMetadata.getTempSampleProjectDir().getCanonicalPath(), "/working-directory");
13 | } catch (IOException e) {
14 | throw new UncheckedIOException(e);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/rule/Sample.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.gradle.exemplar.test.rule;
18 |
19 | import org.apache.commons.io.FileUtils;
20 | import org.junit.rules.TemporaryFolder;
21 | import org.junit.rules.TestRule;
22 | import org.junit.runner.Description;
23 | import org.junit.runners.model.Statement;
24 |
25 | import java.io.Closeable;
26 | import java.io.File;
27 | import java.io.IOException;
28 |
29 | import static org.junit.Assert.assertNotNull;
30 |
31 | /**
32 | * A JUnit rule which copies a sample into the test directory before the test executes.
33 | *
34 | * Looks for a {@link UsesSample} annotation on the test method to determine which sample the
35 | * test requires. If not found, uses the default sample provided in the constructor.
36 | */
37 | public class Sample implements TestRule {
38 |
39 | private final SourceSampleDirSupplier sourceSampleDirSupplier;
40 | private TargetBaseDirSupplier targetBaseDirSupplier;
41 | private String defaultSampleName;
42 |
43 | private String sampleName;
44 | private File targetDir;
45 |
46 | public static Sample from(final String sourceBaseDirPath) {
47 | return from(new SourceSampleDirSupplier() {
48 | @Override
49 | public File getDir(String sampleName) {
50 | return new File(sourceBaseDirPath, sampleName);
51 | }
52 | });
53 | }
54 |
55 | public static Sample from(SourceSampleDirSupplier sourceSampleDirSupplier) {
56 | return new Sample(sourceSampleDirSupplier);
57 | }
58 |
59 | private Sample(SourceSampleDirSupplier sourceSampleDirSupplier) {
60 | this.sourceSampleDirSupplier = sourceSampleDirSupplier;
61 | }
62 |
63 | /**
64 | * Copy the samples into the supplied {@link TemporaryFolder}.
65 | *
66 | * @deprecated please use {@link #intoTemporaryFolder()} or {@link #intoTemporaryFolder(File)}
67 | */
68 | @Deprecated
69 | public Sample into(final TemporaryFolder temporaryFolder) {
70 | return into(new TargetBaseDirSupplier() {
71 | @Override
72 | public File getDir() {
73 | try {
74 | return temporaryFolder.newFolder("samples");
75 | } catch (IOException e) {
76 | throw new RuntimeException("Could not create samples target base dir", e);
77 | }
78 | }
79 | });
80 | }
81 |
82 | /**
83 | * Copy the samples into a temporary folder that is attempted to be deleted afterwards.
84 | */
85 | public Sample intoTemporaryFolder() {
86 | return intoTemporaryFolder(null);
87 | }
88 |
89 | /**
90 | * Copy the samples into a temporary folder that is attempted to be deleted afterwards.
91 | *
92 | * @param parentFolder The parent folder of the created temporary folder
93 | */
94 | public Sample intoTemporaryFolder(File parentFolder) {
95 | return into(new ManagedTemporaryFolder(parentFolder));
96 | }
97 |
98 | /**
99 | * Copy the samples into a folder returned by the supplied {@link TargetBaseDirSupplier}.
100 | *
101 | * @see TargetBaseDirSupplier
102 | */
103 | public Sample into(TargetBaseDirSupplier targetBaseDirSupplier) {
104 | this.targetBaseDirSupplier = targetBaseDirSupplier;
105 | return this;
106 | }
107 |
108 | public Sample withDefaultSample(String name) {
109 | this.defaultSampleName = name;
110 | return this;
111 | }
112 |
113 | public interface SourceSampleDirSupplier {
114 | File getDir(String sampleName);
115 | }
116 |
117 | /**
118 | * Supplier for the base directory into which samples are copied.
119 | *
120 | * May optionally implement {@link Closeable} in which case it will be called after test execution to clean up.
121 | */
122 | public interface TargetBaseDirSupplier {
123 | File getDir();
124 | }
125 |
126 | @Override
127 | public Statement apply(final Statement base, Description description) {
128 | if (targetBaseDirSupplier == null) {
129 | intoTemporaryFolder();
130 | }
131 | sampleName = getSampleName(description);
132 | return new Statement() {
133 | @Override
134 | public void evaluate() throws Throwable {
135 | assertNotNull("No sample selected. Please use @UsesSample or withDefaultSample()", sampleName);
136 | try {
137 | File srcDir = sourceSampleDirSupplier.getDir(sampleName);
138 | FileUtils.copyDirectory(srcDir, getDir());
139 | base.evaluate();
140 | } finally {
141 | if (targetBaseDirSupplier instanceof Closeable) {
142 | ((Closeable) targetBaseDirSupplier).close();
143 | }
144 | }
145 | }
146 | };
147 | }
148 |
149 | private String getSampleName(Description description) {
150 | UsesSample annotation = description.getAnnotation(UsesSample.class);
151 | return annotation != null
152 | ? annotation.value()
153 | : defaultSampleName;
154 | }
155 |
156 | public File getDir() {
157 | if (targetDir == null) {
158 | targetDir = computeSampleDir();
159 | }
160 | return targetDir;
161 | }
162 |
163 | private File computeSampleDir() {
164 | String subDirName = getSampleTargetDirName();
165 | return new File(targetBaseDirSupplier.getDir(), subDirName);
166 | }
167 |
168 | private String getSampleTargetDirName() {
169 | if (sampleName == null) {
170 | throw new IllegalStateException("This rule hasn't been applied, yet.");
171 | }
172 | return sampleName;
173 | }
174 |
175 | private static class ManagedTemporaryFolder implements TargetBaseDirSupplier, Closeable {
176 | private final TemporaryFolder temporaryFolder;
177 |
178 | public ManagedTemporaryFolder(File parentFolder) {
179 | this.temporaryFolder = new TemporaryFolder(parentFolder);
180 | }
181 |
182 | @Override
183 | public File getDir() {
184 | try {
185 | temporaryFolder.create();
186 | return temporaryFolder.getRoot();
187 | } catch (IOException e) {
188 | throw new RuntimeException("Could not create samples target base dir", e);
189 | }
190 | }
191 |
192 | @Override
193 | public void close() {
194 | temporaryFolder.delete();
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/rule/UsesSample.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.rule;
17 |
18 | import java.lang.annotation.Documented;
19 | import java.lang.annotation.ElementType;
20 | import java.lang.annotation.Inherited;
21 | import java.lang.annotation.Retention;
22 | import java.lang.annotation.RetentionPolicy;
23 | import java.lang.annotation.Target;
24 |
25 | /**
26 | * Specifies the relative path of the sample to use for a test.
27 | *
28 | * @see Sample
29 | */
30 | @Documented
31 | @Inherited
32 | @Retention(RetentionPolicy.RUNTIME)
33 | @Target(ElementType.METHOD)
34 | public @interface UsesSample {
35 | String value();
36 | }
37 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/runner/EmbeddedSamplesRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import org.gradle.exemplar.loader.SamplesDiscovery;
19 | import org.gradle.exemplar.model.Sample;
20 | import org.junit.runners.model.InitializationError;
21 |
22 | import java.io.File;
23 | import java.io.IOException;
24 | import java.util.List;
25 |
26 | public class EmbeddedSamplesRunner extends SamplesRunner {
27 |
28 | /**
29 | * Constructs a new {@code ParentRunner} that will run {@code @TestClass}
30 | *
31 | * @param testClass reference to test class being run
32 | */
33 | public EmbeddedSamplesRunner(Class> testClass) throws InitializationError {
34 | super(testClass);
35 | }
36 |
37 | @Override
38 | protected List getChildren() {
39 | File samplesRootDir = getSamplesRootDir();
40 | try {
41 | return SamplesDiscovery.embeddedSamples(samplesRootDir);
42 | } catch (IOException e) {
43 | throw new RuntimeException("Could not extract samples from " + samplesRootDir, e);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/runner/GradleEmbeddedSamplesRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import org.gradle.exemplar.loader.SamplesDiscovery;
19 | import org.gradle.exemplar.model.Sample;
20 | import org.junit.runners.model.InitializationError;
21 |
22 | import java.io.IOException;
23 | import java.util.List;
24 |
25 | /**
26 | * A custom implementation of {@link SamplesRunner} that uses the Gradle Tooling API to execute sample builds.
27 | */
28 | public class GradleEmbeddedSamplesRunner extends GradleSamplesRunner {
29 | public GradleEmbeddedSamplesRunner(Class> testClass) throws InitializationError {
30 | super(testClass);
31 | }
32 |
33 | @Override
34 | protected List getChildren() {
35 | try {
36 | return SamplesDiscovery.embeddedSamples(getSamplesRootDir());
37 | } catch (IOException e) {
38 | throw new RuntimeException("Could not extract embedded samples", e);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/runner/GradleSamplesRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import org.gradle.api.JavaVersion;
19 | import org.gradle.exemplar.executor.CliCommandExecutor;
20 | import org.gradle.exemplar.executor.CommandExecutor;
21 | import org.gradle.exemplar.executor.ExecutionMetadata;
22 | import org.gradle.exemplar.executor.GradleRunnerCommandExecutor;
23 | import org.gradle.exemplar.model.Command;
24 | import org.gradle.exemplar.model.Sample;
25 | import org.junit.Rule;
26 | import org.junit.rules.TemporaryFolder;
27 | import org.junit.runners.model.InitializationError;
28 |
29 | import javax.annotation.Nullable;
30 | import java.io.File;
31 |
32 | /**
33 | * A custom implementation of {@link SamplesRunner} that uses the Gradle Tooling API to execute sample builds.
34 | */
35 | public class GradleSamplesRunner extends SamplesRunner {
36 | private static final String GRADLE_EXECUTABLE = "gradle";
37 | @Rule
38 | public TemporaryFolder tempGradleUserHomeDir = new TemporaryFolder();
39 | private File customGradleInstallation = null;
40 |
41 | public GradleSamplesRunner(Class> testClass) throws InitializationError {
42 | super(testClass);
43 | }
44 |
45 | /**
46 | * Gradle samples tests are ignored on Java 7 and below.
47 | */
48 | @Override
49 | protected boolean isIgnored(Sample child) {
50 | return !JavaVersion.current().isJava8Compatible();
51 | }
52 |
53 | @Override
54 | protected CommandExecutor selectExecutor(ExecutionMetadata executionMetadata, File workingDir, Command command) {
55 | boolean expectFailure = command.isExpectFailure();
56 | if (command.getExecutable().equals(GRADLE_EXECUTABLE)) {
57 | return new GradleRunnerCommandExecutor(workingDir, customGradleInstallation, expectFailure);
58 | }
59 | return new CliCommandExecutor(workingDir);
60 | }
61 |
62 | @Nullable
63 | @Override
64 | protected File getImplicitSamplesRootDir() {
65 | String gradleHomeDir = getCustomGradleInstallationFromSystemProperty();
66 | if (System.getProperty("integTest.samplesdir") != null) {
67 | String samplesRootProperty = System.getProperty("integTest.samplesdir", gradleHomeDir + "/samples");
68 | return new File(samplesRootProperty);
69 | } else if (customGradleInstallation != null) {
70 | return new File(customGradleInstallation, "samples");
71 | } else {
72 | return null;
73 | }
74 | }
75 |
76 | @Nullable
77 | private String getCustomGradleInstallationFromSystemProperty() {
78 | // Allow Gradle installation and samples root dir to be set from a system property
79 | // This is to allow Gradle to test Gradle installations during integration testing
80 | final String gradleHomeDirProperty = System.getProperty("integTest.gradleHomeDir");
81 | if (gradleHomeDirProperty != null) {
82 | File customGradleInstallationDir = new File(gradleHomeDirProperty);
83 | if (customGradleInstallationDir.exists()) {
84 | this.customGradleInstallation = customGradleInstallationDir;
85 | } else {
86 | throw new RuntimeException(String.format("Custom Gradle installation dir at %s was not found", gradleHomeDirProperty));
87 | }
88 | }
89 | return gradleHomeDirProperty;
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/runner/SampleModifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import org.gradle.exemplar.model.Sample;
19 |
20 | /**
21 | * Modifies a given {@link Sample} before it is processed.
22 | */
23 | public interface SampleModifier {
24 | Sample modify(Sample sampleIn);
25 | }
26 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/runner/SampleModifiers.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import java.lang.annotation.*;
19 |
20 | /**
21 | * Specifies execution update classes to invoke before the execution.
22 | */
23 | @Documented
24 | @Inherited
25 | @Target(ElementType.TYPE)
26 | @Retention(RetentionPolicy.RUNTIME)
27 | public @interface SampleModifiers {
28 | Class extends SampleModifier>[] value();
29 | }
30 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/runner/SamplesOutputNormalizers.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import org.gradle.exemplar.test.normalizer.OutputNormalizer;
19 |
20 | import java.lang.annotation.*;
21 |
22 | /**
23 | * Specifies output normalizer classes to invoke on a given subset of samples.
24 | *
25 | * @see SamplesRunner
26 | */
27 | @Documented
28 | @Inherited
29 | @Target(ElementType.TYPE)
30 | @Retention(RetentionPolicy.RUNTIME)
31 | public @interface SamplesOutputNormalizers {
32 | Class extends OutputNormalizer>[] value();
33 | }
34 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/runner/SamplesRoot.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import java.lang.annotation.*;
19 |
20 | /**
21 | * Specifies the directory to find samples, be they external or embedded.
22 | *
23 | * This directory is relative to project where Exemplar is invoked.
24 | *
25 | * For example, given this structure:
26 | *
27 | *
28 | * monorepo/
29 | * ├── build.gradle
30 | * ├── subprojectBar/
31 | * │ └── build.gradle
32 | * │ └── src/
33 | * │ ├── samples/
34 | * │ │ └── bar.adoc
35 | * │ └── test/
36 | * │ └── java/
37 | * │ └── DocsSampleTest.java
38 | * └── subprojectFoo/
39 | * └── src/
40 | *
41 | *
42 | * ...DocsSampleTest should declare @AsciidocSourcesRoot("src/samples").
43 | *
44 | * @see SamplesRunner
45 | */
46 | @Documented
47 | @Inherited
48 | @Target(ElementType.TYPE)
49 | @Retention(RetentionPolicy.RUNTIME)
50 | public @interface SamplesRoot {
51 | String value();
52 | }
53 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/runner/Transformer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | /**
19 | * A {@code Transformer} transforms objects of type.
20 | *
21 | * Implementations are free to return new objects or mutate the incoming value.
22 | *
23 | * @param The type the value is transformed to.
24 | * @param The type of the value to be transformed.
25 | */
26 | public interface Transformer {
27 | /**
28 | * Transforms the given object, and returns the transformed value.
29 | *
30 | * @param in The object to update.
31 | * @return The transformed object.
32 | */
33 | OUT transform(IN in);
34 | }
35 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/verifier/AnyOrderLineSegmentedOutputVerifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.verifier;
17 |
18 | import org.apache.commons.lang3.StringUtils;
19 | import org.junit.Assert;
20 |
21 | import java.util.Arrays;
22 | import java.util.LinkedList;
23 |
24 | public class AnyOrderLineSegmentedOutputVerifier implements OutputVerifier {
25 | private static final String NEWLINE = System.getProperty("line.separator");
26 |
27 | public void verify(final String expected, final String actual, final boolean allowAdditionalOutput) {
28 | // ArrayList does not support removal, and deletions for linked lists are O(1)
29 | LinkedList expectedLines = new LinkedList<>(Arrays.asList(expected.replaceAll("(\\r?\\n)+", "\n").split("\\r?\\n")));
30 | LinkedList unmatchedLines = new LinkedList<>(Arrays.asList(actual.replaceAll("(\\r?\\n)+", "\n").split("\\r?\\n")));
31 |
32 | for (String expectedLine : expectedLines) {
33 | String matchedLine = null;
34 | for (String unmatchedLine : unmatchedLines) {
35 | if (unmatchedLine.equals(expectedLine)) {
36 | matchedLine = unmatchedLine;
37 | }
38 | }
39 | if (matchedLine != null) {
40 | unmatchedLines.remove(matchedLine);
41 | } else {
42 | Assert.fail(String.format("Line missing from output.%n%s%n---%nActual output:%n%s%n---", expectedLine, actual));
43 | }
44 | }
45 |
46 | if (!(allowAdditionalOutput || unmatchedLines.isEmpty())) {
47 | String unmatched = StringUtils.join(unmatchedLines, NEWLINE);
48 | Assert.fail(String.format("Extra lines in output.%n%s%n---%nActual output:%n%s%n---", unmatched, actual));
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/verifier/OutputVerifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.verifier;
17 |
18 | public interface OutputVerifier {
19 | void verify(final String expected, final String actual, final boolean allowAdditionalOutput);
20 | }
21 |
--------------------------------------------------------------------------------
/samples-check/src/main/java/org/gradle/exemplar/test/verifier/StrictOrderLineSegmentedOutputVerifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.verifier;
17 |
18 | import org.junit.Assert;
19 |
20 | import java.util.Arrays;
21 | import java.util.List;
22 |
23 | public class StrictOrderLineSegmentedOutputVerifier implements OutputVerifier {
24 | public void verify(final String expected, final String actual, final boolean allowAdditionalOutput) {
25 | List expectedLines = Arrays.asList(expected.split("\\r?\\n"));
26 | List actualLines = Arrays.asList(actual.split("\\r?\\n"));
27 |
28 | int expectedIndex = 0;
29 | int actualIndex = 0;
30 | if (allowAdditionalOutput) {
31 | actualIndex = findFirstMatchingLine(actualLines, expectedLines.get(expectedIndex));
32 | }
33 | for (; actualIndex < actualLines.size() && expectedIndex < expectedLines.size(); actualIndex++, expectedIndex++) {
34 | final String expectedLine = expectedLines.get(expectedIndex);
35 | final String actualLine = actualLines.get(actualIndex);
36 | if (!expectedLine.equals(actualLine)) {
37 | if (expectedLine.contains(actualLine)) {
38 | Assert.fail(String.format("Missing text at line %d.%nExpected: %s%nActual: %s%nActual output:%n%s%n", actualIndex + 1, expectedLine, actualLine, actual));
39 | }
40 | if (actualLine.contains(expectedLine)) {
41 | Assert.fail(String.format("Extra text at line %d.%nExpected: %s%nActual: %s%nActual output:%n%s%n", actualIndex + 1, expectedLine, actualLine, actual));
42 | }
43 | Assert.fail(String.format("Unexpected value at line %d.%nExpected: %s%nActual: %s%nActual output:%n%s%n", actualIndex + 1, expectedLine, actualLine, actual));
44 | }
45 | }
46 |
47 | if (actualIndex == actualLines.size() && expectedIndex < expectedLines.size()) {
48 | Assert.fail(String.format("Lines missing from actual result, starting at expected line %d.%nExpected: %s%nActual output:%n%s%n", expectedIndex, expectedLines.get(expectedIndex), actual));
49 | }
50 | if (!allowAdditionalOutput && actualIndex < actualLines.size() && expectedIndex == expectedLines.size()) {
51 | Assert.fail(String.format("Extra lines in actual result, starting at line %d.%nActual: %s%nActual output:%n%s%n", actualIndex + 1, actualLines.get(actualIndex), actual));
52 | }
53 | }
54 |
55 | private int findFirstMatchingLine(List actualLines, String expected) {
56 | int index = 0;
57 | for (; index < actualLines.size(); index++) {
58 | if (actualLines.get(index).equals(expected)) {
59 | return index;
60 | }
61 | }
62 | return actualLines.size();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/samples-check/src/test/docs/embedded-test.adoc:
--------------------------------------------------------------------------------
1 | = Embedding sample configuration
2 |
3 | This doc demonstrates how one can declare embedded commands instead of using `.sample.conf` files.
4 |
5 | == Embedded commands example
6 |
7 | Here we see sources that are `include`d, and inline commands and output.
8 |
9 | [.testable-sample,dir="src/test/samples/cli/quickstart"]
10 | .CLI quickstart sample
11 | ====
12 |
13 | .sample.sh
14 | [source,bash]
15 | ----
16 | include::src/test/samples/cli/quickstart/sample.sh[]
17 | ----
18 |
19 | [.sample-command]
20 | ----
21 | $ bash sample.sh
22 | hello, world
23 | ----
24 |
25 | ====
26 |
27 | == Embedded sources and commands example
28 |
29 | This example embeds all the things.
30 |
31 | [.testable-sample]
32 | .Gradle custom logging example
33 | ====
34 |
35 | .build.gradle
36 | [source,groovy]
37 | ----
38 | task compile {
39 | doLast {
40 | println "compiling source"
41 | }
42 | }
43 | task testCompile(dependsOn: compile) {
44 | doLast {
45 | println "compiling test source"
46 | }
47 | }
48 | task test(dependsOn: [compile, testCompile]) {
49 | doLast {
50 | println "running unit tests"
51 | }
52 | }
53 | task build(dependsOn: [test])
54 | ----
55 |
56 | .init.gradle
57 | [source,groovy]
58 | ----
59 | useLogger(new CustomEventLogger())
60 |
61 | class CustomEventLogger extends BuildAdapter implements TaskExecutionListener {
62 |
63 | public void beforeExecute(Task task) {
64 | println "[$task.name]"
65 | }
66 |
67 | public void afterExecute(Task task, TaskState state) {
68 | println()
69 | }
70 |
71 | public void buildFinished(BuildResult result) {
72 | println 'build completed'
73 | if (result.failure != null) {
74 | result.failure.printStackTrace()
75 | }
76 | }
77 | }
78 | ----
79 |
80 | [.sample-command,allow-disordered-output=true]
81 | ----
82 | $ gradle -I init.gradle build
83 |
84 | > Task :compile
85 | [compile]
86 | compiling source
87 |
88 |
89 | > Task :testCompile
90 | [testCompile]
91 | compiling test source
92 |
93 |
94 | > Task :test
95 | [test]
96 | running unit tests
97 |
98 |
99 | > Task :build
100 | [build]
101 |
102 | build completed
103 | 3 actionable tasks: 3 executed
104 | ----
105 |
106 | ====
107 |
108 | == Multi-step sample
109 |
110 | [.testable-sample]
111 | ====
112 |
113 | .sample.sh
114 | [source]
115 | ----
116 | #!/usr/bin/env bash
117 |
118 | echo "dir = `basename $PWD`"
119 | ----
120 |
121 | Create a directory:
122 |
123 | [.sample-command]
124 | ----
125 | $ mkdir demo
126 | $ cd demo
127 | ----
128 |
129 | Run the script:
130 |
131 | [.sample-command]
132 | ----
133 | $ bash ../sample.sh
134 | dir = demo
135 | ----
136 |
137 | ====
138 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/AsciidoctorAnnotationOutputNormalizerTest.groovy:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.normalizer
2 |
3 | import org.gradle.exemplar.executor.ExecutionMetadata
4 | import spock.lang.Specification
5 | import spock.lang.Subject
6 |
7 | @Subject(AsciidoctorAnnotationOutputNormalizer)
8 | class AsciidoctorAnnotationOutputNormalizerTest extends Specification {
9 | def "removes Asciidoctor annotation"() {
10 | given:
11 | OutputNormalizer normalizer = new AsciidoctorAnnotationOutputNormalizer()
12 | String input = """
13 | |./build/install
14 | |├── main
15 | |│ └── debug
16 | |│ ├── building-cpp-applications // <1>
17 | |│ └── lib
18 | |│ └── building-cpp-applications // <2>
19 | |└── test
20 | | ├── building-cpp-applicationsTest // <1>
21 | | └── lib
22 | | └── building-cpp-applicationsTest // <3>
23 | |
24 | |5 directories, 4 files""".stripMargin()
25 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
26 |
27 | expect:
28 | def result = normalizer.normalize(input, executionMetadata)
29 | !result.contains('// <1>')
30 | !result.contains('// <2>')
31 | !result.contains('// <3>')
32 | }
33 |
34 | def "strip trailing whitespace for aligning Asciidoctor annotation"() {
35 | given:
36 | OutputNormalizer normalizer = new AsciidoctorAnnotationOutputNormalizer()
37 | String input = """
38 | |./build/install
39 | |├── main
40 | |│ └── debug
41 | |│ ├── building-cpp-applications // <1>
42 | |│ └── lib
43 | |│ └── building-cpp-applications // <2>
44 | |└── test
45 | | ├── building-cpp-applicationsTest // <1>
46 | | └── lib
47 | | └── building-cpp-applicationsTest // <3>
48 | |
49 | |5 directories, 4 files""".stripMargin()
50 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
51 |
52 | expect:
53 | !(input =~ /\s+$/).find()
54 | def result = normalizer.normalize(input, executionMetadata)
55 | !result.contains('// <1>')
56 | !(result =~ /\s+$/).find()
57 | }
58 |
59 | def "does not remove leading new lines"() {
60 | given:
61 | OutputNormalizer normalizer = new AsciidoctorAnnotationOutputNormalizer()
62 | String input = """
63 | |./build/install
64 | |├── main
65 | |│ └── debug
66 | |│ ├── building-cpp-applications // <1>
67 | |│ └── lib
68 | |│ └── building-cpp-applications // <2>
69 | |└── test
70 | | ├── building-cpp-applicationsTest // <1>
71 | | └── lib
72 | | └── building-cpp-applicationsTest // <3>
73 | |
74 | |5 directories, 4 files
75 | |""".stripMargin()
76 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
77 |
78 | expect:
79 | def result = normalizer.normalize(input, executionMetadata)
80 | result.startsWith('\n')
81 | }
82 |
83 | def "does not remove trailing new lines"() {
84 | given:
85 | OutputNormalizer normalizer = new AsciidoctorAnnotationOutputNormalizer()
86 | String input = """
87 | |./build/install
88 | |├── main
89 | |│ └── debug
90 | |│ ├── building-cpp-applications // <1>
91 | |│ └── lib
92 | |│ └── building-cpp-applications // <2>
93 | |└── test
94 | | ├── building-cpp-applicationsTest // <1>
95 | | └── lib
96 | | └── building-cpp-applicationsTest // <3>
97 | |
98 | |5 directories, 4 files
99 | |""".stripMargin()
100 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
101 |
102 | expect:
103 | def result = normalizer.normalize(input, executionMetadata)
104 | result.endsWith('\n')
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/FileSeparatorOutputNormalizerTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.normalizer
17 |
18 | import org.gradle.exemplar.executor.ExecutionMetadata
19 | import spock.lang.Specification
20 |
21 | class FileSeparatorOutputNormalizerTest extends Specification {
22 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, Collections.emptyMap())
23 | FileSeparatorOutputNormalizer normalizer = new FileSeparatorOutputNormalizer()
24 |
25 | def "avoids normalizing strings that aren't file paths"() {
26 | expect:
27 | normalizer.normalize("anything", executionMetadata) == "anything"
28 | normalizer.normalize("", executionMetadata) == ""
29 | normalizer.normalize("foo /--- bar", executionMetadata, '\\' as char) == "foo /--- bar"
30 | normalizer.normalize("foo /--- bar", executionMetadata, '/' as char) == "foo /--- bar"
31 | normalizer.normalize("foo /--- bar", executionMetadata, File.separatorChar) == "foo /--- bar"
32 | }
33 |
34 | def "replaces all file separators in paths to unix-style"() {
35 | expect:
36 | normalizer.normalize("Path C:\\Users\\username\\dir", executionMetadata, '\\' as char) == "Path C:/Users/username/dir"
37 | }
38 |
39 | def "does not remove leading new lines"() {
40 | given:
41 | OutputNormalizer normalizer = new FileSeparatorOutputNormalizer()
42 | String input = """
43 | |BUILD SUCCESSFUL
44 | |2 actionable tasks: 2 executed
45 | |""".stripMargin()
46 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
47 |
48 | expect:
49 | def result = normalizer.normalize(input, executionMetadata)
50 | result.startsWith('\n')
51 | }
52 |
53 | def "does not remove trailing new lines"() {
54 | given:
55 | OutputNormalizer normalizer = new FileSeparatorOutputNormalizer()
56 | String input = """
57 | |Path C:\\Users\\username\\dir
58 | |""".stripMargin()
59 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
60 |
61 | expect:
62 | def result = normalizer.normalize(input, executionMetadata)
63 | result.endsWith('\n')
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/LeadingNewLineOutputNormalizerTest.groovy:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.normalizer
2 |
3 | import org.gradle.exemplar.executor.ExecutionMetadata
4 | import spock.lang.Specification
5 | import spock.lang.Subject
6 |
7 | @Subject(LeadingNewLineOutputNormalizer)
8 | class LeadingNewLineOutputNormalizerTest extends Specification {
9 | def "can normalize empty output"() {
10 | given:
11 | OutputNormalizer normalizer = new LeadingNewLineOutputNormalizer()
12 | String input = ''
13 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
14 |
15 | when:
16 | def result = normalizer.normalize(input, executionMetadata)
17 |
18 | then:
19 | noExceptionThrown()
20 |
21 | and:
22 | result == ''
23 | }
24 |
25 | def "can normalize one line of output"() {
26 | given:
27 | OutputNormalizer normalizer = new LeadingNewLineOutputNormalizer()
28 | String input = 'Some output'
29 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
30 |
31 | when:
32 | def result = normalizer.normalize(input, executionMetadata)
33 |
34 | then:
35 | noExceptionThrown()
36 |
37 | and:
38 | result == 'Some output'
39 | }
40 |
41 | def "does not remove trailing new lines"() {
42 | given:
43 | OutputNormalizer normalizer = new LeadingNewLineOutputNormalizer()
44 | String input = """
45 | |BUILD SUCCESSFUL
46 | |2 actionable tasks: 2 executed
47 | |""".stripMargin()
48 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
49 |
50 | expect:
51 | def result = normalizer.normalize(input, executionMetadata)
52 | result.endsWith('\n')
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/StripTrailingOutputNormalizerTest.groovy:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.normalizer
2 |
3 | import org.gradle.exemplar.executor.ExecutionMetadata
4 | import spock.lang.Specification
5 | import spock.lang.Subject
6 |
7 | @Subject(StripTrailingOutputNormalizer)
8 | class StripTrailingOutputNormalizerTest extends Specification {
9 | def "can remove trailing spaces at the end of each output line"() {
10 | given:
11 | OutputNormalizer normalizer = new StripTrailingOutputNormalizer()
12 | String input = """
13 | |BUILD SUCCESSFUL
14 | |2 actionable tasks: 2 executed
15 | |""".stripMargin()
16 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
17 |
18 | expect:
19 | def result = normalizer.normalize(input, executionMetadata)
20 | (input =~ /[ ]+$/).find()
21 | !(result =~ /[ ]+$/).find()
22 | }
23 |
24 | def "does not remove leading new lines"() {
25 | given:
26 | OutputNormalizer normalizer = new StripTrailingOutputNormalizer()
27 | String input = """
28 | |BUILD SUCCESSFUL
29 | |2 actionable tasks: 2 executed
30 | |""".stripMargin()
31 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
32 |
33 | expect:
34 | def result = normalizer.normalize(input, executionMetadata)
35 | result.startsWith('\n')
36 | }
37 |
38 | def "does not remove trailing new lines"() {
39 | given:
40 | OutputNormalizer normalizer = new StripTrailingOutputNormalizer()
41 | String input = """
42 | |BUILD SUCCESSFUL
43 | |2 actionable tasks: 2 executed
44 | |""".stripMargin()
45 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
46 |
47 | expect:
48 | def result = normalizer.normalize(input, executionMetadata)
49 | result.endsWith('\n')
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/TrailingNewLineOutputNormalizerTest.groovy:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.normalizer
2 |
3 | import org.gradle.exemplar.executor.ExecutionMetadata
4 | import spock.lang.Specification
5 | import spock.lang.Subject
6 |
7 | @Subject(TrailingNewLineOutputNormalizer)
8 | class TrailingNewLineOutputNormalizerTest extends Specification {
9 | def "can remove empty line at the end of the output"() {
10 | given:
11 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer()
12 | String input = '''
13 | |BUILD SUCCESSFUL
14 | |2 actionable tasks: 2 executed
15 | |'''.stripMargin()
16 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
17 |
18 | expect:
19 | def result = normalizer.normalize(input, executionMetadata)
20 | !result.endsWith('\n')
21 | }
22 |
23 | def "can remove multiple empty line at the end of the output"() {
24 | given:
25 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer()
26 | String input = '''
27 | |BUILD SUCCESSFUL
28 | |2 actionable tasks: 2 executed
29 | |
30 | |
31 | |'''.stripMargin()
32 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
33 |
34 | expect:
35 | def result = normalizer.normalize(input, executionMetadata)
36 | !result.endsWith('\n')
37 | }
38 |
39 | def "can normalize empty output"() {
40 | given:
41 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer()
42 | String input = ''
43 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
44 |
45 | when:
46 | def result = normalizer.normalize(input, executionMetadata)
47 |
48 | then:
49 | noExceptionThrown()
50 |
51 | and:
52 | result == ''
53 | }
54 |
55 | def "can normalize one line of output"() {
56 | given:
57 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer()
58 | String input = 'Some output'
59 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
60 |
61 | when:
62 | def result = normalizer.normalize(input, executionMetadata)
63 |
64 | then:
65 | noExceptionThrown()
66 |
67 | and:
68 | result == 'Some output'
69 | }
70 |
71 | def "does not remove leading new lines"() {
72 | given:
73 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer()
74 | String input = """
75 | |BUILD SUCCESSFUL
76 | |2 actionable tasks: 2 executed
77 | |""".stripMargin()
78 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:])
79 |
80 | expect:
81 | def result = normalizer.normalize(input, executionMetadata)
82 | result.startsWith('\n')
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/WorkingDirectoryOutputNormalizerTest.groovy:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.normalizer
2 |
3 | import org.gradle.exemplar.executor.ExecutionMetadata
4 | import spock.lang.Specification
5 | import spock.lang.Subject
6 |
7 | @Subject(WorkingDirectoryOutputNormalizer)
8 | class WorkingDirectoryOutputNormalizerTest extends Specification {
9 | def "can remove working path"() {
10 | given:
11 | OutputNormalizer normalizer = new WorkingDirectoryOutputNormalizer()
12 | String input = """
13 | |Some output with a temporary path: /private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663/demo/build/classes/java/main
14 | |BUILD SUCCESSFUL
15 | |2 actionable tasks: 2 executed
16 | |""".stripMargin()
17 | ExecutionMetadata executionMetadata = new ExecutionMetadata(new File('/private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663'), [:])
18 |
19 | expect:
20 | def result = normalizer.normalize(input, executionMetadata)
21 | !result.contains('/private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663')
22 | result.contains('/working-directory/demo/build/classes/java/main')
23 | }
24 |
25 | def "does not remove leading new lines"() {
26 | given:
27 | OutputNormalizer normalizer = new WorkingDirectoryOutputNormalizer()
28 | String input = """
29 | |BUILD SUCCESSFUL
30 | |2 actionable tasks: 2 executed
31 | |""".stripMargin()
32 | ExecutionMetadata executionMetadata = new ExecutionMetadata(new File('/private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663'), [:])
33 |
34 | expect:
35 | def result = normalizer.normalize(input, executionMetadata)
36 | result.startsWith('\n')
37 | }
38 |
39 | def "does not remove trailing new lines"() {
40 | given:
41 | OutputNormalizer normalizer = new WorkingDirectoryOutputNormalizer()
42 | String input = """
43 | |Some output with a temporary path: /private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663/demo/build/classes/java/main
44 | |BUILD SUCCESSFUL
45 | |2 actionable tasks: 2 executed
46 | |""".stripMargin()
47 | ExecutionMetadata executionMetadata = new ExecutionMetadata(new File('/private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663'), [:])
48 |
49 | expect:
50 | def result = normalizer.normalize(input, executionMetadata)
51 | result.endsWith('\n')
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/rule/SampleTest.groovy:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.rule
2 |
3 | import org.junit.ClassRule
4 | import org.junit.Rule
5 | import org.junit.Test
6 | import org.junit.experimental.runners.Enclosed
7 | import org.junit.rules.RuleChain
8 | import org.junit.rules.TemporaryFolder
9 | import org.junit.rules.TestRule
10 | import org.junit.runner.RunWith
11 |
12 | @RunWith(Enclosed.class)
13 | class SampleTest {
14 |
15 | static class WithTemporaryFolderRule extends SampleTestCases {
16 |
17 | public TemporaryFolder temporaryFolder = new TemporaryFolder()
18 | Sample sample = Sample.from("src/test/samples/gradle")
19 | .into(temporaryFolder)
20 | .withDefaultSample("basic-sample")
21 |
22 | @Rule
23 | public TestRule ruleChain = RuleChain.outerRule(temporaryFolder).around(sample)
24 | }
25 |
26 | static class WithImplicitTemporaryFolder extends SampleTestCases {
27 | @Rule
28 | public Sample sample = Sample.from("src/test/samples/gradle")
29 | .withDefaultSample("basic-sample")
30 |
31 | @Override
32 | Sample getSample() {
33 | return this.sample
34 | }
35 | }
36 |
37 | static class WithExplicitTemporaryFolder extends SampleTestCases {
38 | @ClassRule
39 | public static TemporaryFolder temporaryFolder = new TemporaryFolder()
40 | @Rule
41 | public Sample sample = Sample.from("src/test/samples/gradle")
42 | .intoTemporaryFolder(temporaryFolder.getRoot())
43 | .withDefaultSample("basic-sample")
44 |
45 | @Override
46 | Sample getSample() {
47 | return this.sample
48 | }
49 | }
50 |
51 | static abstract class SampleTestCases {
52 | abstract Sample getSample()
53 |
54 | @Test
55 | void "copies default sample"() {
56 | File sampleDir = sample.dir
57 | assert sampleDir.directory
58 | assert new File(sampleDir, "build.gradle").file
59 | }
60 |
61 | @UsesSample("composite-sample/basic")
62 | @Test
63 | void "copies sample from annotation"() {
64 | File sampleDir = sample.dir
65 | assert sample.dir.directory
66 | assert new File(sampleDir, "compositeBuildsBasicCli.sample.out").file
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/runner/BrokenSampleDiscoveryIntegrationTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner
17 |
18 | import org.junit.Rule
19 | import org.junit.rules.TemporaryFolder
20 | import org.junit.platform.engine.discovery.DiscoverySelectors
21 | import org.junit.platform.launcher.Launcher
22 | import org.junit.platform.launcher.LauncherDiscoveryRequest
23 | import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder
24 | import org.junit.platform.launcher.core.LauncherFactory
25 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener
26 | import spock.lang.Specification
27 |
28 | class BrokenSampleDiscoveryIntegrationTest extends Specification {
29 | @Rule
30 | TemporaryFolder tmpDir = new TemporaryFolder()
31 |
32 | def "start JUnit vintage engine via launcher"() {
33 | given:
34 | def brokenSample = tmpDir.newFile("broken.sample.conf")
35 | brokenSample << """
36 | executable: sleep
37 | args: 1
38 | expected-output-file: not-exist.sample.out
39 | """.stripMargin()
40 |
41 | def testClass = """
42 | package org.gradle.exemplar.test;
43 |
44 | import org.gradle.exemplar.test.runner.SamplesRunner;
45 | import org.gradle.exemplar.test.runner.SamplesRoot;
46 | import org.junit.runner.RunWith;
47 |
48 | @RunWith(SamplesRunner.class)
49 | @SamplesRoot("${tmpDir.root.absolutePath}")
50 | public class SimpleJUnit4Test {
51 | }
52 | """
53 |
54 | def testClassFile = tmpDir.newFile("SimpleJUnit4Test.java")
55 | testClassFile.text = testClass
56 |
57 | def compiler = new GroovyClassLoader()
58 | def compiledClass = compiler.parseClass(testClassFile)
59 |
60 | when:
61 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
62 | .selectors(DiscoverySelectors.selectClass(compiledClass))
63 | .build()
64 |
65 | Launcher launcher = LauncherFactory.create()
66 | def listener = new SummaryGeneratingListener()
67 | launcher.registerTestExecutionListeners(listener)
68 |
69 | launcher.execute(request)
70 |
71 | then:
72 | listener.summary.testsFailedCount == 1
73 | listener.summary.failures[0].exception.message.contains("Could not read sample definition")
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/runner/CollectingNotifier.groovy:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.runner
2 |
3 | import org.junit.runner.Description
4 | import org.junit.runner.notification.Failure
5 | import org.junit.runner.notification.RunNotifier
6 | import org.junit.runner.notification.StoppedByUserException
7 |
8 |
9 | class CollectingNotifier extends RunNotifier {
10 | final List tests = []
11 | final List failures = []
12 |
13 | @Override
14 | void fireTestStarted(Description description) throws StoppedByUserException {
15 | tests.add(description)
16 | }
17 |
18 | @Override
19 | void fireTestFailure(Failure failure) {
20 | failures.add(failure)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/runner/SamplesRunnerIntegrationTest.groovy:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.runner
2 |
3 | import org.junit.experimental.categories.Category
4 | import org.junit.runner.Request
5 | import org.junit.runner.RunWith
6 | import spock.lang.Specification
7 |
8 | class SamplesRunnerIntegrationTest extends Specification {
9 | def "runs samples-check CLI samples"() {
10 | def notifier = new CollectingNotifier()
11 |
12 | when:
13 | Request.aClass(HappyDaySamples.class).runner.run(notifier)
14 |
15 | then:
16 | notifier.tests.size() == 2
17 | notifier.tests[0].methodName == 'multi-step_multi-step.sample'
18 | notifier.tests[0].className == HappyDaySamples.class.name
19 |
20 | notifier.tests[1].methodName == 'quickstart_quickstart.sample'
21 | notifier.tests[1].className == HappyDaySamples.class.name
22 |
23 | notifier.failures.empty
24 | }
25 |
26 | @RunWith(SamplesRunner.class)
27 | @SamplesRoot("src/test/samples/cli")
28 | @Category(CoveredByTests)
29 | static class HappyDaySamples {}
30 |
31 | def "can use multi-steps with working directory inside sample"() {
32 | def notifier = new CollectingNotifier()
33 |
34 | when:
35 | Request.aClass(HappyDayWithWorkingDirectorySamples.class).runner.run(notifier)
36 |
37 | then:
38 | notifier.tests.size() == 1
39 | notifier.tests[0].methodName == 'multi-step_multi-step.sample'
40 | notifier.tests[0].className == HappyDayWithWorkingDirectorySamples.class.name
41 |
42 | notifier.failures.empty
43 | }
44 |
45 | @RunWith(SamplesRunner.class)
46 | @SamplesRoot("src/test/samples/cli-with-working-directory")
47 | @Category(CoveredByTests)
48 | static class HappyDayWithWorkingDirectorySamples {}
49 |
50 | def "warn when using working directory after change directory command instruction"() {
51 | def notifier = new CollectingNotifier()
52 |
53 | when:
54 | Request.aClass(HappyDayWithWorkingDirectoryAndChangeDirectoryCommandSamples.class).runner.run(notifier)
55 |
56 | then:
57 | notifier.tests.size() == 1
58 | notifier.tests[0].methodName == 'multi-step_multi-step.sample'
59 | notifier.tests[0].className == HappyDayWithWorkingDirectoryAndChangeDirectoryCommandSamples.class.name
60 |
61 | notifier.failures.empty
62 | }
63 |
64 | @RunWith(SamplesRunner.class)
65 | @SamplesRoot("src/test/samples/cli-with-working-directory-and-change-directory")
66 | @Category(CoveredByTests)
67 | static class HappyDayWithWorkingDirectoryAndChangeDirectoryCommandSamples {}
68 | }
69 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/runner/SamplesRunnerSadDayIntegrationTest.groovy:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.runner
2 |
3 | import org.junit.Rule
4 | import org.junit.experimental.categories.Category
5 | import org.junit.rules.TemporaryFolder
6 | import org.junit.runner.Request
7 | import org.junit.runner.RunWith
8 | import spock.lang.Specification
9 |
10 | class SamplesRunnerSadDayIntegrationTest extends Specification {
11 | @Rule
12 | TemporaryFolder tmpDir = new TemporaryFolder()
13 |
14 | def "tests fail when command fails"() {
15 | def notifier = new CollectingNotifier()
16 |
17 | when:
18 | Request.aClass(HasBadCommand.class).runner.run(notifier)
19 |
20 | then:
21 | notifier.tests.size() == 1
22 | notifier.tests[0].methodName == '_broken-command.sample'
23 | notifier.tests[0].className == HasBadCommand.class.name
24 |
25 | notifier.failures.size() == 1
26 | notifier.failures[0].description == notifier.tests[0]
27 |
28 | def expectedOutput = """
29 | Expected sample invocation to succeed but it failed.
30 | Command was: 'bash broken'
31 | Working directory: '.+/_broken-command.sample'
32 | \\[BEGIN OUTPUT\\]
33 | bash: broken: No such file or directory
34 |
35 | \\[END OUTPUT\\]
36 | """.stripIndent(true).trim()
37 | notifier.failures[0].message.trim() ==~ /${expectedOutput}/
38 | }
39 |
40 | def "tests fail when command produces unexpected output"() {
41 | def notifier = new CollectingNotifier()
42 |
43 | when:
44 | Request.aClass(HasBadOutput.class).runner.run(notifier)
45 |
46 | then:
47 | notifier.tests.size() == 1
48 | notifier.tests[0].methodName == '_broken-output.sample'
49 | notifier.tests[0].className == HasBadOutput.class.name
50 |
51 | notifier.failures.size() == 1
52 | notifier.failures[0].description == notifier.tests[0]
53 | notifier.failures[0].message.trim() == """
54 | Missing text at line 1.
55 | Expected: not a thing
56 | Actual: thing
57 | Actual output:
58 | thing
59 | """.stripIndent(true).trim()
60 | }
61 |
62 | @SamplesRoot("src/test/resources/broken/command")
63 | @RunWith(SamplesRunner)
64 | @Category(CoveredByTests)
65 | static class HasBadCommand {}
66 |
67 | @SamplesRoot("src/test/resources/broken/output")
68 | @RunWith(SamplesRunner)
69 | @Category(CoveredByTests)
70 | static class HasBadOutput {}
71 | }
72 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/verifier/AnyOrderLineSegmentedOutputVerifierTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.verifier
17 |
18 | import spock.lang.Specification
19 |
20 | class AnyOrderLineSegmentedOutputVerifierTest extends Specification {
21 | private static final String NL = System.getProperty("line.separator")
22 | OutputVerifier verifier = new AnyOrderLineSegmentedOutputVerifier()
23 |
24 | def "checks all expected lines exist in any order with no extra output"() {
25 | given:
26 | String expected = """
27 | message 1
28 | message 2
29 | """
30 | String actual = """
31 | message 2
32 | message 1
33 | """
34 |
35 | when:
36 | verifier.verify(expected, actual, false)
37 |
38 | then:
39 | notThrown(AssertionError)
40 | }
41 |
42 | def "checks all expected lines exist in any order with extra output"() {
43 | given:
44 | String expected = """
45 | message 1
46 | message 2
47 | """
48 | String actual = """
49 | `
50 | message 1
51 |
52 |
53 | extra output
54 | """
55 |
56 | when:
57 | verifier.verify(expected, actual, true)
58 |
59 | then:
60 | AssertionError assertionError = thrown(AssertionError)
61 | assertionError.message.contains("""Line missing from output.${NL}message 2""")
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/samples-check/src/test/groovy/org/gradle/exemplar/test/verifier/StrictOrderLineSegmentedOutputVerifierTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.verifier
17 |
18 | import spock.lang.Specification
19 |
20 | class StrictOrderLineSegmentedOutputVerifierTest extends Specification {
21 | private static final String NL = System.getProperty("line.separator")
22 | OutputVerifier verifier = new StrictOrderLineSegmentedOutputVerifier()
23 |
24 | def "checks all expected lines exist in sequential order disallowing extra output"() {
25 | given:
26 | String expected = """
27 | message 1
28 | message 2
29 | """
30 | String actual = """
31 | message 1
32 | message 2
33 | """
34 |
35 | when:
36 | verifier.verify(expected, actual, false)
37 |
38 | then:
39 | notThrown(AssertionError)
40 | }
41 |
42 | def "checks all expected lines exist in sequential order with extra output"() {
43 | given:
44 | String expected = """
45 | message 1
46 | message 2
47 | """
48 | String actual = """
49 | message 1
50 | message 2
51 |
52 | extra logs
53 | """
54 |
55 | when:
56 | verifier.verify(expected, actual, true)
57 |
58 | then:
59 | notThrown(AssertionError)
60 | }
61 |
62 | def "checks all expected lines exist in sequential order with extra output at the beginning"() {
63 | given:
64 | String expected = """message 1
65 | message 2
66 | """
67 | String actual = """
68 | extra logs
69 |
70 | message 1
71 | message 2
72 | """
73 |
74 | when:
75 | verifier.verify(expected, actual, true)
76 |
77 | then:
78 | notThrown(AssertionError)
79 | }
80 |
81 | def "fails when expected lines not found while disallowing extra output"() {
82 | given:
83 | String expected = """
84 | message 1
85 | message 2
86 | """
87 | String actual = """
88 | message 2
89 | message 1
90 | """
91 |
92 | when:
93 | verifier.verify(expected, actual, false)
94 |
95 | then:
96 | AssertionError assertionError = thrown(AssertionError)
97 | assertionError.message.contains("""Unexpected value at line 2.${NL}Expected: message 1${NL}Actual: message 2""")
98 | }
99 |
100 | def "fails with extra output while disallowing extra output"() {
101 | given:
102 | String expected = """
103 | message 1
104 | message 2
105 | """
106 | String actual = """
107 | message 1
108 | message 2
109 |
110 | extra logs
111 | """
112 |
113 | when:
114 | verifier.verify(expected, actual, false)
115 |
116 | then:
117 | AssertionError assertionError = thrown(AssertionError)
118 | assertionError.message.contains('Extra lines in actual result, starting at line 4.')
119 | }
120 |
121 | def "fails when expected lines not found while allowing extra output"() {
122 | given:
123 | String expected = """message 1
124 | message 2
125 | """
126 | String actual = """
127 | extra logs
128 |
129 | message 3
130 | message 4
131 |
132 | extra logs 2
133 | """
134 |
135 | when:
136 | verifier.verify(expected, actual, true)
137 |
138 | then:
139 | def error = thrown(AssertionError)
140 | error.message.contains('Lines missing from actual result, starting at expected line 0.')
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/samples-check/src/test/java/org/gradle/exemplar/test/runner/CliSamplesRunnerIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | // tag::source[]
19 | import org.junit.runner.RunWith;
20 |
21 | @RunWith(SamplesRunner.class)
22 | @SamplesRoot("src/test/samples/cli")
23 | public class CliSamplesRunnerIntegrationTest {
24 | }
25 | // end::source[]
26 |
--------------------------------------------------------------------------------
/samples-check/src/test/java/org/gradle/exemplar/test/runner/CoveredByTests.java:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.runner;
2 |
3 | /**
4 | * Indicates test classes that are exercised by some other test (eg broken tests).
5 | */
6 | public interface CoveredByTests {
7 | }
8 |
--------------------------------------------------------------------------------
/samples-check/src/test/java/org/gradle/exemplar/test/runner/EmbeddedSamplesRunnerIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import org.junit.runner.RunWith;
19 |
20 | @RunWith(GradleEmbeddedSamplesRunner.class)
21 | @SamplesRoot("src/test/docs")
22 | public class EmbeddedSamplesRunnerIntegrationTest {
23 | }
24 |
--------------------------------------------------------------------------------
/samples-check/src/test/java/org/gradle/exemplar/test/runner/GradleSamplesRunnerIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import org.gradle.exemplar.test.normalizer.FileSeparatorOutputNormalizer;
19 | import org.gradle.exemplar.test.normalizer.GradleOutputNormalizer;
20 | import org.gradle.exemplar.test.normalizer.JavaObjectSerializationOutputNormalizer;
21 | import org.junit.runner.RunWith;
22 |
23 | @RunWith(GradleSamplesRunner.class)
24 | @SamplesRoot("src/test/samples/gradle")
25 | // tag::sample-output-normalizers[]
26 | @SamplesOutputNormalizers({JavaObjectSerializationOutputNormalizer.class, FileSeparatorOutputNormalizer.class, GradleOutputNormalizer.class})
27 | // end::sample-output-normalizers[]
28 | public class GradleSamplesRunnerIntegrationTest {
29 | }
30 |
--------------------------------------------------------------------------------
/samples-check/src/test/java/org/gradle/exemplar/test/runner/SampleModifierIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.test.runner;
17 |
18 | import org.gradle.exemplar.test.runner.modifiers.ExtraCommandArgumentsSampleModifier;
19 | import org.junit.runner.RunWith;
20 |
21 | @RunWith(GradleSamplesRunner.class)
22 | @SamplesRoot("src/test/samples/customization")
23 | @SampleModifiers({ExtraCommandArgumentsSampleModifier.class})
24 | public class SampleModifierIntegrationTest {
25 | }
26 |
--------------------------------------------------------------------------------
/samples-check/src/test/java/org/gradle/exemplar/test/runner/modifiers/ExtraCommandArgumentsSampleModifier.java:
--------------------------------------------------------------------------------
1 | package org.gradle.exemplar.test.runner.modifiers;
2 |
3 | import org.gradle.exemplar.model.Command;
4 | import org.gradle.exemplar.model.Sample;
5 | import org.gradle.exemplar.test.runner.SampleModifier;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class ExtraCommandArgumentsSampleModifier implements SampleModifier {
11 | @Override
12 | public Sample modify(Sample sampleIn) {
13 | List newCommands = new ArrayList<>();
14 | for (Command command : sampleIn.getCommands()) {
15 | List args = new ArrayList<>(command.getArgs());
16 | args.add("printProperty");
17 | args.add("-DmyProp=myValue");
18 | newCommands.add(command.toBuilder().setArgs(args).build());
19 | }
20 | return new Sample(sampleIn.getId(), sampleIn.getProjectDir(), newCommands);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/samples-check/src/test/resources/broken/command/broken-command.sample.conf:
--------------------------------------------------------------------------------
1 | executable = bash
2 | args = broken
3 |
--------------------------------------------------------------------------------
/samples-check/src/test/resources/broken/output/broken-output.sample.conf:
--------------------------------------------------------------------------------
1 | executable = echo
2 | args = thing
3 | expected-output-file: sample.out
4 |
--------------------------------------------------------------------------------
/samples-check/src/test/resources/broken/output/sample.out:
--------------------------------------------------------------------------------
1 | not a thing
2 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/multi-step.sample.conf:
--------------------------------------------------------------------------------
1 | commands: [{
2 | executable = "mkdir"
3 | execution-subdirectory = "workDir"
4 | args = "demo"
5 | }, {
6 | executable = "cd"
7 | args = "workDir"
8 | }, {
9 | executable = "bash"
10 | execution-subdirectory = "demo"
11 | args = "../../sample.sh"
12 | expected-output-file = "sample.out"
13 | }]
14 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/sample.out:
--------------------------------------------------------------------------------
1 | dir = demo
2 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/sample.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "dir = `basename $PWD`"
4 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/workDir/.placeholder:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gradle/exemplar/02b6c53c344ca792be380c92eb20682a89eb288a/samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/workDir/.placeholder
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli-with-working-directory/multi-step/multi-step.sample.conf:
--------------------------------------------------------------------------------
1 | commands: [{
2 | executable = "mkdir"
3 | execution-subdirectory = "workDir"
4 | args = "demo"
5 | }, {
6 | executable = "bash"
7 | execution-subdirectory = "workDir/demo"
8 | args = "../../sample.sh"
9 | expected-output-file = "sample.out"
10 | }]
11 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli-with-working-directory/multi-step/sample.out:
--------------------------------------------------------------------------------
1 | dir = demo
2 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli-with-working-directory/multi-step/sample.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "dir = `basename $PWD`"
4 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli-with-working-directory/multi-step/workDir/.placeholder:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gradle/exemplar/02b6c53c344ca792be380c92eb20682a89eb288a/samples-check/src/test/samples/cli-with-working-directory/multi-step/workDir/.placeholder
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli/multi-step/multi-step.sample.conf:
--------------------------------------------------------------------------------
1 | commands: [{
2 | executable = "mkdir"
3 | args = "demo"
4 | }, {
5 | executable = "cd"
6 | args = "demo"
7 | }, {
8 | executable = "bash"
9 | args = "../sample.sh"
10 | expected-output-file = "sample.out"
11 | }]
12 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli/multi-step/sample.out:
--------------------------------------------------------------------------------
1 | dir = demo
2 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli/multi-step/sample.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "dir = `basename $PWD`"
4 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli/quickstart/quickstart.sample.conf:
--------------------------------------------------------------------------------
1 | executable: bash
2 | args: sample.sh
3 | expected-output-file: quickstart.sample.out
4 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli/quickstart/quickstart.sample.out:
--------------------------------------------------------------------------------
1 | hello, world
2 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/cli/quickstart/sample.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "hello, world"
4 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/customization/customization-sample/build.gradle:
--------------------------------------------------------------------------------
1 | task hello {
2 | doLast {
3 | println("hello")
4 | }
5 | }
6 |
7 | task printProperty {
8 | doLast {
9 | println("myProp: ${System.getProperty("myProp")}")
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/customization/customization-sample/hello.sample.conf:
--------------------------------------------------------------------------------
1 | executable: gradle
2 | args: hello
3 | flags: -q
4 | expected-output-file: hello.sample.out
5 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/customization/customization-sample/hello.sample.out:
--------------------------------------------------------------------------------
1 | hello
2 | myProp: myValue
3 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/basic-sample/build.gradle:
--------------------------------------------------------------------------------
1 | task hello {
2 | doLast {
3 | println("hello, world")
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/basic-sample/hello.sample.conf:
--------------------------------------------------------------------------------
1 | executable: gradle
2 | args: hello
3 | flags: -q
4 | expected-output-file: hello.sample.out
5 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/basic-sample/hello.sample.out:
--------------------------------------------------------------------------------
1 | hello, world
2 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/build-init-sample/build-init.sample.conf:
--------------------------------------------------------------------------------
1 | commands = [
2 | {
3 | executable = "mkdir"
4 | args = "demo"
5 | },
6 | {
7 | executable = "cd"
8 | args = "demo"
9 | },
10 | {
11 | executable = "gradle"
12 | args = "init"
13 | },
14 | {
15 | executable = "gradle"
16 | args = "projects"
17 | expected-output-file = "sample.out"
18 | }
19 | ]
20 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/build-init-sample/sample.out:
--------------------------------------------------------------------------------
1 |
2 | > Task :projects
3 |
4 | Projects:
5 |
6 | ------------------------------------------------------------
7 | Root project 'demo'
8 | ------------------------------------------------------------
9 |
10 | Root project 'demo'
11 | No sub-projects
12 |
13 | To see a list of the tasks of a project, run gradle :tasks
14 | For example, try running gradle :tasks
15 |
16 | BUILD SUCCESSFUL in 0s
17 | 1 actionable task: 1 executed
18 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/composite/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'idea'
2 |
3 | defaultTasks 'run'
4 |
5 | // START SNIPPET run
6 | task run {
7 | dependsOn gradle.includedBuild('my-app').task(':run')
8 | }
9 | // END SNIPPET run
10 |
11 | task checkAll {
12 | dependsOn gradle.includedBuild('my-app').task(':check')
13 | dependsOn gradle.includedBuild('my-utils').task(':number-utils:check')
14 | dependsOn gradle.includedBuild('my-utils').task(':string-utils:check')
15 | }
16 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/composite/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name='adhoc'
2 |
3 | includeBuild '../my-app'
4 | includeBuild '../my-utils'
5 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/compositeBuildsBasicCli.sample.conf:
--------------------------------------------------------------------------------
1 | executable: gradle
2 | execution-subdirectory: my-app
3 | args: run
4 | flags: "--include-build=../my-utils"
5 | expect-failure: false
6 | expected-output-file: "compositeBuildsBasicCli.sample.out"
7 | allow-additional-output: true
8 | allow-disordered-output: true
9 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/compositeBuildsBasicCli.sample.out:
--------------------------------------------------------------------------------
1 | > Task :processResources NO-SOURCE
2 | > Task :my-utils:string-utils:compileJava
3 | > Task :my-utils:string-utils:processResources NO-SOURCE
4 | > Task :my-utils:string-utils:classes
5 | > Task :my-utils:string-utils:jar
6 | > Task :my-utils:number-utils:compileJava
7 | > Task :my-utils:number-utils:processResources NO-SOURCE
8 | > Task :my-utils:number-utils:classes
9 | > Task :my-utils:number-utils:jar
10 | > Task :compileJava
11 | > Task :classes
12 |
13 | > Task :run
14 | The answer is 42
15 |
16 | BUILD SUCCESSFUL in 0s
17 | 6 actionable tasks: 6 executed
18 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/my-app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'application'
3 | apply plugin: 'idea'
4 |
5 | group "org.sample"
6 | version "1.0"
7 |
8 | mainClassName = "org.sample.myapp.Main"
9 |
10 | dependencies {
11 | implementation "org.sample:number-utils:1.0"
12 | implementation "org.sample:string-utils:1.0"
13 | }
14 |
15 | repositories {
16 | mavenCentral()
17 | }
18 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/my-app/settings-composite.gradle:
--------------------------------------------------------------------------------
1 | // Needs to be used with --settings-file settings-composite.gradle
2 |
3 | rootProject.name = 'my-app'
4 |
5 | includeBuild '../my-utils'
6 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/my-app/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'my-app'
2 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/my-app/src/main/java/org/sample/myapp/Main.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.sample.myapp;
18 |
19 | import org.sample.numberutils.Numbers;
20 | import org.sample.stringutils.Strings;
21 |
22 | public class Main {
23 |
24 | public static void main(String... args) {
25 | new Main().printAnswer();
26 | }
27 |
28 | public void printAnswer() {
29 | String output = Strings.concat(" The answer is ", Numbers.add(19, 23));
30 | System.out.println(output);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/my-utils/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'idea'
2 |
3 | subprojects {
4 | apply plugin: 'java'
5 | apply plugin: 'idea'
6 |
7 | group "org.sample"
8 | version "1.0"
9 |
10 | repositories {
11 | mavenCentral()
12 | }
13 | }
14 |
15 | project(":string-utils") {
16 | dependencies {
17 | implementation "org.apache.commons:commons-lang3:3.12.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/my-utils/number-utils/src/main/java/org/sample/numberutils/Numbers.java:
--------------------------------------------------------------------------------
1 | package org.sample.numberutils;
2 |
3 | public class Numbers {
4 | public static int add(int left, int right) { return left + right; }
5 | }
6 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/my-utils/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'my-utils'
2 |
3 | include 'number-utils', 'string-utils'
4 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/composite-sample/basic/my-utils/string-utils/src/main/java/org/sample/stringutils/Strings.java:
--------------------------------------------------------------------------------
1 | package org.sample.stringutils;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 |
5 | public class Strings {
6 | public static String concat(Object left, Object right) {
7 | return strip(left) + " " + strip(right);
8 | }
9 |
10 | private static String strip(Object val) {
11 | return StringUtils.strip(String.valueOf(val));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/dual-dsl-sample/groovy/build.gradle:
--------------------------------------------------------------------------------
1 | tasks.create("sayHello") {
2 | doLast {
3 | println('Hello, world!')
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/dual-dsl-sample/groovy/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'dsl-sample'
2 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/dual-dsl-sample/helpTask.out:
--------------------------------------------------------------------------------
1 |
2 | > Task :sayHello
3 | Hello, world!
4 |
5 | BUILD SUCCESSFUL in 0s
6 | 1 actionable task: 1 executed
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/dual-dsl-sample/helpTask.sample.conf:
--------------------------------------------------------------------------------
1 | commands: [{
2 | executable: gradle
3 | execution-subdirectory: groovy
4 | args: sayHello
5 | expected-output-file: helpTask.out
6 | }, {
7 | executable: gradle
8 | execution-subdirectory: kotlin
9 | args: sayHello
10 | expected-output-file: helpTask.out
11 | }]
12 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/dual-dsl-sample/kotlin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | tasks.create("sayHello") {
2 | doLast {
3 | println("Hello, world!")
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/dual-dsl-sample/kotlin/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "dsl-sample"
2 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/multi-step-sample/build.gradle:
--------------------------------------------------------------------------------
1 | import org.gradle.work.ChangeType
2 | import org.gradle.work.Incremental
3 | import org.gradle.work.InputChanges
4 |
5 | def inputsDir = layout.projectDirectory.dir("inputs")
6 |
7 | task originalInputs() {
8 | doLast {
9 | inputsDir.asFile.mkdir()
10 | inputsDir.file('1.txt').asFile.text = 'Content for file 1.'
11 | inputsDir.file('2.txt').asFile.text = 'Content for file 2.'
12 | inputsDir.file('3.txt').asFile.text = 'Content for file 3.'
13 | }
14 | }
15 |
16 | // START SNIPPET updated-inputs
17 | task updateInputs() {
18 | doLast {
19 | inputsDir.file('1.txt').asFile.text = 'Changed content for existing file 1.'
20 | inputsDir.file('4.txt').asFile.text = 'Content for new file 4.'
21 | }
22 | }
23 | // END SNIPPET updated-inputs
24 |
25 | // START SNIPPET removed-input
26 | task removeInput() {
27 | doLast {
28 | inputsDir.file('3.txt').asFile.delete()
29 | }
30 | }
31 | // END SNIPPET removed-input
32 |
33 | // START SNIPPET removed-output
34 | task removeOutput() {
35 | doLast {
36 | layout.buildDirectory.file('outputs/1.txt').get().asFile.delete()
37 | }
38 | }
39 | // END SNIPPET removed-output
40 |
41 | // START SNIPPET reverse
42 | task incrementalReverse(type: IncrementalReverseTask) {
43 | inputDir = inputsDir.asFileTree
44 | outputDir = layout.buildDirectory.dir('outputs')
45 | inputProperty = project.properties['taskInputProperty'] ?: 'original'
46 | }
47 | // END SNIPPET reverse
48 |
49 | // START SNIPPET incremental-task
50 | class IncrementalReverseTask extends DefaultTask {
51 | @InputFiles
52 | @SkipWhenEmpty
53 | FileCollection inputDir
54 |
55 | @OutputDirectory
56 | Provider outputDir
57 |
58 | @Input
59 | def inputProperty
60 |
61 | @TaskAction
62 | void execute(InputChanges inputs) {
63 | println inputs.incremental ? 'CHANGED inputs considered out of date'
64 | : 'ALL inputs considered out of date'
65 | // START SNIPPET handle-non-incremental-inputs
66 | if (!inputs.incremental)
67 | project.delete(outputDir.get().asFile.listFiles())
68 | // END SNIPPET handle-non-incremental-inputs
69 |
70 | if (inputs.incremental) {
71 | inputs.getFileChanges(inputDir).each { change ->
72 | // START SNIPPET out-of-date-inputs
73 | if (change.changeType == ChangeType.MODIFIED && change.file.file) {
74 | println "out of date: ${change.file.name}"
75 | def targetFile = new File(outputDir.get().asFile, change.file.name)
76 | targetFile.text = change.file.text.reverse()
77 | }
78 | // END SNIPPET out-of-date-inputs
79 | // START SNIPPET added-inputs
80 | if (change.changeType == ChangeType.ADDED && change.file.file) {
81 | println "added: ${change.file.name}"
82 | def targetFile = new File(outputDir.get().asFile, change.file.name)
83 | targetFile.text = change.file.text.reverse()
84 | }
85 | // END SNIPPET added-inputs
86 |
87 | // START SNIPPET removed-inputs
88 | if (change.changeType == ChangeType.REMOVED) {
89 | println "removed: ${change.file.name}"
90 | def targetFile = new File(outputDir.get().asFile, change.file.name)
91 | targetFile.delete()
92 | }
93 | // END SNIPPET removed-inputs
94 | }
95 | }
96 | }
97 | }
98 | // END SNIPPET incremental-task
99 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/multi-step-sample/incrementalTaskRemovedOutput.out:
--------------------------------------------------------------------------------
1 | CHANGED inputs considered out of date
2 | out of date: 1.txt
3 | added: 4.txt
4 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/multi-step-sample/incrementalTaskRemovedOutput.sample.conf:
--------------------------------------------------------------------------------
1 | commands: [{
2 | executable: gradle
3 | args: originalInputs
4 | }, {
5 | executable: gradle
6 | args: incrementalReverse
7 | expected-output-file: originalInputs.out
8 | allow-additional-output: true
9 | }, {
10 | executable: gradle
11 | args: removeOutput updateInputs
12 | }, {
13 | executable: gradle
14 | args: incrementalReverse
15 | expected-output-file: incrementalTaskRemovedOutput.out
16 | allow-disordered-output: true
17 | allow-additional-output: true
18 | }]
19 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/multi-step-sample/originalInputs.out:
--------------------------------------------------------------------------------
1 | > Task :incrementalReverse
2 | ALL inputs considered out of date
3 |
--------------------------------------------------------------------------------
/samples-check/src/test/samples/gradle/multi-step-sample/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'incremental-task'
2 |
--------------------------------------------------------------------------------
/samples-discovery/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("exemplar.java-conventions")
3 | id("exemplar.publishing-conventions")
4 | }
5 |
6 | dependencies {
7 | compileOnly(libs.jsr305)
8 | implementation(libs.asciidoctorj)
9 | implementation(libs.commons.io)
10 | implementation(libs.commons.lang3)
11 | implementation(libs.typesafe.config)
12 | testImplementation(libs.groovy)
13 | testImplementation(libs.bundles.spock)
14 | }
15 |
--------------------------------------------------------------------------------
/samples-discovery/src/main/java/org/gradle/exemplar/InvalidSampleException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar;
17 |
18 | public class InvalidSampleException extends RuntimeException {
19 | public InvalidSampleException(String message) {
20 | super(message);
21 | }
22 |
23 | public InvalidSampleException(String message, Exception cause) {
24 | super(message, cause);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/samples-discovery/src/main/java/org/gradle/exemplar/loader/CommandsParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.loader;
17 |
18 | import com.typesafe.config.*;
19 | import org.gradle.exemplar.InvalidSampleException;
20 | import org.gradle.exemplar.model.Command;
21 |
22 | import java.io.File;
23 | import java.io.IOException;
24 | import java.nio.charset.Charset;
25 | import java.nio.file.Files;
26 | import java.nio.file.Path;
27 | import java.nio.file.Paths;
28 | import java.util.*;
29 |
30 | public class CommandsParser {
31 | private static final String EXECUTABLE = "executable";
32 | private static final String COMMANDS = "commands";
33 | private static final String EXECUTION_SUBDIRECTORY = "execution-subdirectory";
34 | private static final String ARGS = "args";
35 | private static final String FLAGS = "flags";
36 | private static final String EXPECT_FAILURE = "expect-failure";
37 | private static final String ALLOW_ADDITIONAL_OUTPUT = "allow-additional-output";
38 | private static final String ALLOW_DISORDERED_OUTPUT = "allow-disordered-output";
39 | private static final String EXPECTED_OUTPUT_FILE = "expected-output-file";
40 | private static final String USER_INPUTS = "user-inputs";
41 |
42 | public static List parse(final File sampleConfigFile) {
43 | try {
44 | final Config sampleConfig = ConfigFactory.parseFile(sampleConfigFile, ConfigParseOptions.defaults().setAllowMissing(false)).resolve();
45 | final File sampleProjectDir = sampleConfigFile.getParentFile();
46 |
47 | List commands = new ArrayList<>();
48 | // Allow a single command to be specified without an enclosing list
49 | if (sampleConfig.hasPath(EXECUTABLE)) {
50 | commands.add(parseCommand(sampleConfig, sampleProjectDir));
51 | } else if (sampleConfig.hasPath(COMMANDS)) {
52 | for (Config stepConfig : sampleConfig.getConfigList(COMMANDS)) {
53 | commands.add(parseCommand(stepConfig, sampleProjectDir));
54 | }
55 | } else {
56 | throw new InvalidSampleException("A sample must be defined with an 'executable' or 'commands'");
57 | }
58 |
59 | return commands;
60 | } catch (Exception e) {
61 | throw new InvalidSampleException(String.format("Could not read sample definition from %s.", sampleConfigFile), e);
62 | }
63 | }
64 |
65 | private static Command parseCommand(final Config commandConfig, final File sampleProjectDir) {
66 | // NOTE: A user must specify an executable. This prevents unexpected behavior when an empty or unexpected file is accidentally loaded
67 | String executable;
68 | try {
69 | executable = commandConfig.getString(EXECUTABLE);
70 | } catch (ConfigException e) {
71 | throw new InvalidSampleException("'executable' field cannot be empty", e);
72 | }
73 | final String executionDirectory = ConfigUtil.string(commandConfig, EXECUTION_SUBDIRECTORY, null);
74 | final List commands = ConfigUtil.strings(commandConfig, ARGS, new ArrayList());
75 | final List flags = ConfigUtil.strings(commandConfig, FLAGS, new ArrayList());
76 | String expectedOutput = null;
77 | if (commandConfig.hasPath(EXPECTED_OUTPUT_FILE)) {
78 | final File expectedOutputFile = new File(sampleProjectDir, commandConfig.getString(EXPECTED_OUTPUT_FILE));
79 | try {
80 | final Path path = Paths.get(expectedOutputFile.getAbsolutePath());
81 | expectedOutput = new String(Files.readAllBytes(path), Charset.forName("UTF-8"));
82 | } catch (IOException e) {
83 | throw new InvalidSampleException("Could not read sample output file " + expectedOutputFile.getAbsolutePath(), e);
84 | }
85 | }
86 |
87 | final boolean expectFailures = ConfigUtil.booleanValue(commandConfig, EXPECT_FAILURE, false);
88 | final boolean allowAdditionalOutput = ConfigUtil.booleanValue(commandConfig, ALLOW_ADDITIONAL_OUTPUT, false);
89 | final boolean allowDisorderedOutput = ConfigUtil.booleanValue(commandConfig, ALLOW_DISORDERED_OUTPUT, false);
90 | final List userInputs = ConfigUtil.strings(commandConfig, USER_INPUTS, Collections.emptyList());
91 |
92 | return new Command(executable, executionDirectory, commands, flags, expectedOutput, expectFailures, allowAdditionalOutput, allowDisorderedOutput, userInputs);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/samples-discovery/src/main/java/org/gradle/exemplar/loader/ConfigUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.loader;
17 |
18 | import com.typesafe.config.Config;
19 | import com.typesafe.config.ConfigValue;
20 |
21 | import java.io.File;
22 | import java.util.*;
23 |
24 | class ConfigUtil {
25 | public static Map map(Config config, String key, Map defaultValues) {
26 | if (config.hasPath(key)) {
27 | Map props = new LinkedHashMap<>();
28 | for (Map.Entry entry : config.getConfig(key).entrySet()) {
29 | props.put(entry.getKey(), entry.getValue().unwrapped().toString());
30 | }
31 | return props;
32 | } else {
33 | return defaultValues;
34 | }
35 | }
36 |
37 | public static boolean booleanValue(Config config, String key, boolean defaultValue) {
38 | if (config.hasPath(key)) {
39 | return Boolean.valueOf(config.getString(key));
40 | } else {
41 | return defaultValue;
42 | }
43 | }
44 |
45 | public static String string(Config config, String key, String defaultValue) {
46 | if (config.hasPath(key)) {
47 | return config.getString(key);
48 | } else {
49 | return defaultValue;
50 | }
51 | }
52 |
53 | public static List strings(Config config, String key, List defaults) {
54 | if (config.hasPath(key)) {
55 | Object value = config.getAnyRef(key);
56 | if (value instanceof List) {
57 | List result = new ArrayList<>();
58 | for (Object o : (List) value) {
59 | result.add(o.toString());
60 | }
61 | return result;
62 | } else if (value.toString().length() > 0) {
63 | return Arrays.asList(value.toString().split(" "));
64 | }
65 | }
66 | return defaults;
67 | }
68 |
69 | public static File file(Config config, File projectDir, String key, File defaultValue) {
70 | String fileName = ConfigUtil.string(config, key, null);
71 | if (fileName == null) {
72 | return defaultValue;
73 | } else {
74 | return new File(projectDir, fileName);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/samples-discovery/src/main/java/org/gradle/exemplar/loader/SamplesDiscovery.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.loader;
17 |
18 | import org.apache.commons.io.FileUtils;
19 | import org.apache.commons.io.FilenameUtils;
20 | import org.gradle.exemplar.loader.asciidoctor.AsciidoctorSamplesDiscovery;
21 | import org.gradle.exemplar.model.Command;
22 | import org.gradle.exemplar.model.InvalidSample;
23 | import org.gradle.exemplar.model.Sample;
24 |
25 | import java.io.File;
26 | import java.io.IOException;
27 | import java.util.*;
28 |
29 | public class SamplesDiscovery {
30 | public static List externalSamples(File rootSamplesDir) {
31 | // The .sample.conf suffix makes it clear that this is a HOCON file specifically for samples
32 | return filteredExternalSamples(rootSamplesDir, new String[]{"sample.conf"}, true);
33 | }
34 |
35 | public static List filteredExternalSamples(File rootSamplesDir, String[] fileExtensions, boolean recursive) {
36 | Collection sampleConfigFiles = FileUtils.listFiles(rootSamplesDir, fileExtensions, recursive);
37 |
38 | List samples = new ArrayList<>();
39 | for (File sampleConfigFile : sampleConfigFiles) {
40 | final String id = generateSampleId(rootSamplesDir, sampleConfigFile);
41 | try {
42 | final List commands = CommandsParser.parse(sampleConfigFile);
43 | // FIXME: Currently the temp directory used when running samples-check has a different name.
44 | // This causes Gradle project names to differ when one is not explicitly set in settings.gradle. This should be preserved.
45 | final File sampleProjectDir = sampleConfigFile.getParentFile();
46 | samples.add(new Sample(id, sampleProjectDir, commands));
47 | } catch (Exception e) {
48 | samples.add(new InvalidSample(id, e));
49 | }
50 | }
51 | // Always return (and test) samples in a fixed order
52 | sortSamples(samples);
53 |
54 | return samples;
55 | }
56 |
57 | public static List embeddedSamples(File asciidocSrcDir) throws IOException {
58 | return filteredEmbeddedSamples(asciidocSrcDir, new String[]{"adoc", "asciidoc"}, true);
59 | }
60 |
61 | public static List filteredEmbeddedSamples(File rootSamplesDir, String[] fileExtensions, boolean recursive) throws IOException {
62 | Collection sampleConfigFiles = FileUtils.listFiles(rootSamplesDir, fileExtensions, recursive);
63 |
64 | List samples = new ArrayList<>();
65 | for (File sampleConfigFile : sampleConfigFiles) {
66 | samples.addAll(AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(sampleConfigFile));
67 | }
68 | sortSamples(samples);
69 |
70 | return samples;
71 | }
72 |
73 | private static String generateSampleId(File rootSamplesDir, File scenarioFile) {
74 | String prefix = rootSamplesDir
75 | .toPath()
76 | .relativize(scenarioFile.getParentFile().toPath())
77 | .toString()
78 | .replaceAll("[/\\\\]", "_");
79 | return prefix + "_" + FilenameUtils.removeExtension(scenarioFile.getName());
80 | }
81 |
82 | private static void sortSamples(List samples) {
83 | Collections.sort(samples, new SampleComparator());
84 | }
85 |
86 | private static class SampleComparator implements Comparator {
87 | @Override
88 | public int compare(Sample s1, Sample s2) {
89 | return s1.getId().compareTo(s2.getId());
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/samples-discovery/src/main/java/org/gradle/exemplar/loader/asciidoctor/AsciidoctorCommandsDiscovery.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.loader.asciidoctor;
17 |
18 | import org.asciidoctor.Asciidoctor;
19 | import org.asciidoctor.OptionsBuilder;
20 | import org.asciidoctor.ast.AbstractBlock;
21 | import org.asciidoctor.ast.Block;
22 | import org.asciidoctor.ast.Document;
23 | import org.asciidoctor.ast.ListImpl;
24 | import org.gradle.exemplar.InvalidSampleException;
25 | import org.gradle.exemplar.model.Command;
26 |
27 | import java.io.File;
28 | import java.io.IOException;
29 | import java.util.ArrayDeque;
30 | import java.util.ArrayList;
31 | import java.util.Arrays;
32 | import java.util.Collections;
33 | import java.util.List;
34 | import java.util.Map;
35 | import java.util.Queue;
36 | import java.util.function.Consumer;
37 |
38 | import static org.asciidoctor.OptionsBuilder.options;
39 |
40 | public class AsciidoctorCommandsDiscovery {
41 |
42 | private static final String COMMAND_PREFIX = "$ ";
43 |
44 | public static List extractFromAsciidoctorFile(File documentFile) throws IOException {
45 | return extractFromAsciidoctorFile(documentFile, it -> {});
46 | }
47 |
48 | public static List extractFromAsciidoctorFile(File documentFile, Consumer action) throws IOException {
49 | Asciidoctor asciidoctor = Asciidoctor.Factory.create();
50 |
51 | try {
52 | OptionsBuilder options = options();
53 | action.accept(options);
54 |
55 | Document document = asciidoctor.loadFile(documentFile, options.asMap());
56 | return extractAsciidocCommands(document);
57 | } finally {
58 | asciidoctor.shutdown();
59 | }
60 | }
61 |
62 | private static List extractAsciidocCommands(AbstractBlock testableSampleBlock) {
63 | List commands = new ArrayList<>();
64 | Queue queue = new ArrayDeque<>();
65 | queue.add(testableSampleBlock);
66 | while (!queue.isEmpty()) {
67 | AbstractBlock node = queue.poll();
68 | if (node instanceof ListImpl) {
69 | queue.addAll(((ListImpl) node).getItems());
70 | } else {
71 | for (AbstractBlock child : node.getBlocks()) {
72 | if (child.isBlock() && child.hasRole("sample-command")) {
73 | parseEmbeddedCommand((Block) child, commands);
74 | } else {
75 | queue.offer(child);
76 | }
77 | }
78 | }
79 | }
80 |
81 | return commands;
82 | }
83 |
84 | private static void parseEmbeddedCommand(Block block, List commands) {
85 | Map attributes = block.getAttributes();
86 | String[] lines = block.source().split("\r?\n");
87 | int pos = 0;
88 |
89 | do {
90 | pos = parseOneCommand(lines, pos, attributes, commands);
91 | } while (pos < lines.length);
92 | }
93 |
94 | private static int parseOneCommand(String[] lines, int pos, Map attributes, List commands) {
95 | String commandLine = lines[pos];
96 | if (!commandLine.startsWith(COMMAND_PREFIX)) {
97 | throw new InvalidSampleException("Inline sample command " + commandLine);
98 | }
99 |
100 | String[] commandLineWords = commandLine.substring(COMMAND_PREFIX.length()).split("\\s+");
101 | String executable = commandLineWords[0];
102 |
103 | List args = Collections.emptyList();
104 | if (commandLineWords.length > 1) {
105 | args = Arrays.asList(Arrays.copyOfRange(commandLineWords, 1, commandLineWords.length));
106 | }
107 |
108 | StringBuilder expectedOutput = new StringBuilder();
109 | int nextCommand = pos + 1;
110 | while (nextCommand < lines.length && !lines[nextCommand].startsWith(COMMAND_PREFIX)) {
111 | if (nextCommand > pos + 1) {
112 | expectedOutput.append("\n");
113 | }
114 | expectedOutput.append(lines[nextCommand]);
115 | nextCommand++;
116 | }
117 |
118 | Command command = new Command(executable,
119 | null,
120 | args,
121 | Collections.emptyList(),
122 | expectedOutput.toString(),
123 | attributes.containsKey("expect-failure"),
124 | attributes.containsKey("allow-additional-output"),
125 | attributes.containsKey("allow-disordered-output"),
126 | attributes.containsKey("user-inputs") ? toUserInputs(attributes.get("user-inputs").toString()) : Collections.emptyList());
127 | commands.add(command);
128 | return nextCommand;
129 | }
130 |
131 | private static List toUserInputs(String value) {
132 | return Arrays.asList(value.split("\\|", -1));
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/samples-discovery/src/main/java/org/gradle/exemplar/model/Command.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.model;
17 |
18 | import javax.annotation.Nonnull;
19 | import javax.annotation.Nullable;
20 | import java.util.List;
21 |
22 | public class Command {
23 | private final String executable;
24 | private final String executionSubdirectory;
25 | private final List args;
26 | private final List flags;
27 | private final String expectedOutput;
28 | private final boolean expectFailure;
29 | private final boolean allowAdditionalOutput;
30 | private final boolean allowDisorderedOutput;
31 | private final List userInputs;
32 |
33 | public Command(@Nonnull String executable, @Nullable String executionDirectory, List args, List flags, @Nullable String expectedOutput, boolean expectFailure, boolean allowAdditionalOutput, boolean allowDisorderedOutput, List userInputs) {
34 | this.executable = executable;
35 | this.executionSubdirectory = executionDirectory;
36 | this.args = args;
37 | this.flags = flags;
38 | this.expectedOutput = expectedOutput;
39 | this.expectFailure = expectFailure;
40 | this.allowAdditionalOutput = allowAdditionalOutput;
41 | this.allowDisorderedOutput = allowDisorderedOutput;
42 | this.userInputs = userInputs;
43 | }
44 |
45 | @Nonnull
46 | public String getExecutable() {
47 | return executable;
48 | }
49 |
50 | @Nullable
51 | public String getExecutionSubdirectory() {
52 | return executionSubdirectory;
53 | }
54 |
55 | public List getArgs() {
56 | return args;
57 | }
58 |
59 | public List getFlags() {
60 | return flags;
61 | }
62 |
63 | @Nullable
64 | public String getExpectedOutput() {
65 | return expectedOutput;
66 | }
67 |
68 | /**
69 | * @return true if executing the scenario build is expected to fail.
70 | */
71 | public boolean isExpectFailure() {
72 | return expectFailure;
73 | }
74 |
75 | /**
76 | * @return true if output lines other than those provided are allowed.
77 | */
78 | public boolean isAllowAdditionalOutput() {
79 | return allowAdditionalOutput;
80 | }
81 |
82 | /**
83 | * @return true if actual output lines can differ in order from expected.
84 | */
85 | public boolean isAllowDisorderedOutput() {
86 | return allowDisorderedOutput;
87 | }
88 |
89 | /**
90 | * @return a list of user inputs to provide to the command
91 | */
92 | public List getUserInputs() {
93 | return userInputs;
94 | }
95 |
96 | public Builder toBuilder() {
97 | return new Builder(getExecutable(),
98 | getExecutionSubdirectory(),
99 | getArgs(),
100 | getFlags(),
101 | getExpectedOutput(),
102 | isExpectFailure(),
103 | isAllowAdditionalOutput(),
104 | isAllowDisorderedOutput(),
105 | getUserInputs());
106 | }
107 |
108 | public static class Builder {
109 | private String executable;
110 | private String executionSubdirectory;
111 | private List args;
112 | private List flags;
113 | private String expectedOutput;
114 | private boolean expectFailure;
115 | private boolean allowAdditionalOutput;
116 | private boolean allowDisorderedOutput;
117 | private List userInputs;
118 |
119 | private Builder(String executable, String executionDirectory, List args, List flags, String expectedOutput, boolean expectFailure, boolean allowAdditionalOutput, boolean allowDisorderedOutput, List userInputs) {
120 | this.executable = executable;
121 | this.executionSubdirectory = executionDirectory;
122 | this.args = args;
123 | this.flags = flags;
124 | this.expectedOutput = expectedOutput;
125 | this.expectFailure = expectFailure;
126 | this.allowAdditionalOutput = allowAdditionalOutput;
127 | this.allowDisorderedOutput = allowDisorderedOutput;
128 | this.userInputs = userInputs;
129 | }
130 |
131 | public Builder setExecutable(String executable) {
132 | this.executable = executable;
133 | return this;
134 | }
135 |
136 | public Builder setExecutionSubdirectory(String executionSubdirectory) {
137 | this.executionSubdirectory = executionSubdirectory;
138 | return this;
139 | }
140 |
141 | public Builder setArgs(List args) {
142 | this.args = args;
143 | return this;
144 | }
145 |
146 | public Builder setFlags(List flags) {
147 | this.flags = flags;
148 | return this;
149 | }
150 |
151 | public Builder setExpectedOutput(String expectedOutput) {
152 | this.expectedOutput = expectedOutput;
153 | return this;
154 | }
155 |
156 | public Builder setExpectFailure(boolean expectFailure) {
157 | this.expectFailure = expectFailure;
158 | return this;
159 | }
160 |
161 | public Builder setAllowAdditionalOutput(boolean allowAdditionalOutput) {
162 | this.allowAdditionalOutput = allowAdditionalOutput;
163 | return this;
164 | }
165 |
166 | public Builder setAllowDisorderedOutput(boolean allowDisorderedOutput) {
167 | this.allowDisorderedOutput = allowDisorderedOutput;
168 | return this;
169 | }
170 |
171 | public Command build() {
172 | return new Command(executable, executionSubdirectory, args, flags, expectedOutput, expectFailure, allowAdditionalOutput, allowDisorderedOutput, userInputs);
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/samples-discovery/src/main/java/org/gradle/exemplar/model/InvalidSample.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.model;
17 |
18 | import java.util.Collections;
19 |
20 | public class InvalidSample extends Sample {
21 | private final Exception exception;
22 |
23 | public InvalidSample(String id, Exception exception) {
24 | super(id, null, Collections.emptyList());
25 | this.exception = exception;
26 | }
27 |
28 | public Exception getException() {
29 | return exception;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/samples-discovery/src/main/java/org/gradle/exemplar/model/Sample.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.model;
17 |
18 | import java.io.File;
19 | import java.util.List;
20 |
21 | public class Sample {
22 | private final String id;
23 | private final File projectDir;
24 | private final List commands;
25 |
26 | public Sample(String id, File projectDir, List commands) {
27 | this.id = id;
28 | this.projectDir = projectDir;
29 | this.commands = commands;
30 | }
31 |
32 | public String getId() {
33 | return id;
34 | }
35 |
36 | public File getProjectDir() {
37 | return projectDir;
38 | }
39 |
40 | public List getCommands() {
41 | return commands;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/samples-discovery/src/test/groovy/org/gradle/exemplar/loader/SamplesDiscoveryTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.loader
17 |
18 | import org.gradle.exemplar.model.Sample
19 | import org.junit.Rule
20 | import org.junit.rules.TemporaryFolder
21 | import spock.lang.Specification
22 |
23 | class SamplesDiscoveryTest extends Specification {
24 | @Rule
25 | TemporaryFolder tmpDir = new TemporaryFolder()
26 |
27 | def "discovers nested samples"() {
28 | given:
29 | tmpDir.newFolder("basic-sample")
30 | tmpDir.newFile("basic-sample/default.sample.conf") << "executable: help"
31 | tmpDir.newFolder("advanced-sample", "nested")
32 | tmpDir.newFile("advanced-sample/shallow.sample.conf") << "commands: [{executable: foo}]"
33 | // tmpDir.newFile("advanced-sample/nested/crazy.sample.conf") << "commands: [{executable: build}, {executable: cleanup}]"
34 |
35 | when:
36 | Collection samples = SamplesDiscovery.externalSamples(tmpDir.root)
37 |
38 | then:
39 | samples.size() == 2
40 | }
41 |
42 | def "allows custom file filter"() {
43 | given:
44 | tmpDir.newFolder("first-sample")
45 | tmpDir.newFile("first-sample/default.sample") << "executable: help"
46 | tmpDir.newFolder("src", "play")
47 | tmpDir.newFile("src/play/bogus.conf") << "I'm not a sample file"
48 |
49 | when:
50 | Collection samples = SamplesDiscovery.filteredExternalSamples(tmpDir.root, ["sample"].toArray() as String[], true)
51 |
52 | then:
53 | samples.size() == 1
54 | samples[0].id == "first-sample_default"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/samples-discovery/src/test/groovy/org/gradle/exemplar/loader/asciidoctor/AsciidoctorCommandsDiscoveryTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.loader.asciidoctor
17 |
18 | import org.asciidoctor.AttributesBuilder
19 | import org.asciidoctor.SafeMode
20 | import org.gradle.exemplar.model.Command
21 | import org.junit.Rule
22 | import org.junit.rules.TemporaryFolder
23 | import spock.lang.Specification
24 | import spock.lang.Subject
25 |
26 | @Subject(AsciidoctorCommandsDiscovery)
27 | class AsciidoctorCommandsDiscoveryTest extends Specification {
28 | @Rule
29 | TemporaryFolder tmpDir = new TemporaryFolder()
30 |
31 | def "discovers samples inside an asciidoctor file with sources inline"() {
32 | given:
33 | def file = tmpDir.newFile('sample.adoc') << '''
34 | |= Document Title
35 | |
36 | |[.sample-command,allow-disordered-output=true]
37 | |----
38 | |$ ruby hello.rb
39 | |
40 | |hello, world
41 | |
42 | |some more output
43 | |----
44 | |'''.stripMargin()
45 |
46 | when:
47 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file)
48 |
49 | then:
50 | commands.size() == 1
51 | def command = commands.get(0)
52 | command.executable == 'ruby'
53 | command.args == ['hello.rb']
54 | command.allowDisorderedOutput
55 | command.expectedOutput == '''
56 | |hello, world
57 | |
58 | |some more output'''.stripMargin()
59 | }
60 |
61 | def "sample may include multiple sample-command blocks"() {
62 | given:
63 | def file = tmpDir.newFile('sample.adoc') << '''
64 | |= Document Title
65 | |
66 | |Run this first:
67 | |
68 | |[.sample-command]
69 | |----
70 | |$ ruby hello.rb
71 | |hello, world
72 | |----
73 | |
74 | |Then do this:
75 | |
76 | |[.sample-command]
77 | |----
78 | |$ mkdir some-dir
79 | |----
80 | |'''.stripMargin()
81 |
82 | when:
83 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file)
84 |
85 | then:
86 | commands.size() == 2
87 | def command = commands.get(0)
88 | command.executable == 'ruby'
89 | command.args == ['hello.rb']
90 | command.expectedOutput == 'hello, world'
91 |
92 | def command2 = commands.get(1)
93 | command2.executable == 'mkdir'
94 | command2.args == ['some-dir']
95 | command2.expectedOutput.empty
96 | }
97 |
98 | def "sample-command block may include multiple commands"() {
99 | given:
100 | def file = tmpDir.newFile('sample.adoc') << '''
101 | |= Document Title
102 | |
103 | |Run this first:
104 | |
105 | |[.sample-command]
106 | |----
107 | |$ ruby hello.rb
108 | |
109 | |hello, world
110 | |
111 | |$ mkdir some-dir
112 | |$ cd some-dir
113 | |----
114 | |'''.stripMargin()
115 |
116 | when:
117 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file)
118 |
119 | then:
120 | commands.size() == 3
121 | def command = commands.get(0)
122 | command.executable == 'ruby'
123 | command.args == ['hello.rb']
124 | command.expectedOutput == '''
125 | |hello, world
126 | |'''.stripMargin()
127 |
128 | def command2 = commands.get(1)
129 | command2.executable == 'mkdir'
130 | command2.args == ['some-dir']
131 | command2.expectedOutput.empty
132 |
133 | def command3 = commands.get(2)
134 | command3.executable == 'cd'
135 | command3.args == ['some-dir']
136 | command3.expectedOutput.empty
137 | }
138 |
139 | def "can include data from attributes"() {
140 | given:
141 | def file = tmpDir.newFile('sample.adoc') << '''
142 | |= Document Title
143 | |
144 | |Run this first:
145 | |
146 | |[listing.terminal.sample-command]
147 | |----
148 | |$ pwd
149 | |include::{sampleoutputdir}/pwd-output.txt[]
150 | |----
151 | |'''.stripMargin()
152 | def outputDir = tmpDir.newFolder('output')
153 | tmpDir.newFile('output/pwd-output.txt').text = "${tmpDir.root.getAbsolutePath()}\n"
154 |
155 | when:
156 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file) {
157 | it.attributes(AttributesBuilder.attributes().attribute('sampleoutputdir', outputDir.getAbsolutePath())).safe(SafeMode.UNSAFE)
158 | }
159 |
160 | then:
161 | commands.size() == 1
162 | def command = commands.get(0)
163 | command.executable == 'pwd'
164 | command.args == []
165 | command.expectedOutput == tmpDir.root.getAbsolutePath()
166 | }
167 |
168 | def "can extract commands when using Asciidoctor callout"() {
169 | given:
170 | def file = tmpDir.newFile('sample.adoc') << '''
171 | |= Document Title
172 | |
173 | |[listing.terminal.sample-command]
174 | |----
175 | |$ ./command
176 | |Some output // <1>
177 | |----
178 | |<1> Some callout
179 | |'''.stripMargin()
180 |
181 | when:
182 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file)
183 |
184 | then:
185 | commands.size() == 1
186 | def command = commands.get(0)
187 | command.executable == './command'
188 | command.args == []
189 | command.expectedOutput == 'Some output // <1>'
190 | }
191 |
192 | def "can extract empty user inputs when none is specified"() {
193 | given:
194 | def file = tmpDir.newFile('sample.adoc') << '''
195 | |= Document Title
196 | |
197 | |[listing.terminal.sample-command]
198 | |----
199 | |$ ./command
200 | |Some output
201 | |----
202 | |'''.stripMargin()
203 |
204 | when:
205 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file)
206 |
207 | then:
208 | commands.size() == 1
209 | def command = commands.get(0)
210 | command.userInputs.size() == 0
211 | }
212 |
213 | def "can extract user inputs to the command"() {
214 | given:
215 | def file = tmpDir.newFile('sample.adoc') << '''
216 | |= Document Title
217 | |
218 | |[listing.terminal.sample-command,user-inputs="1||yes"]
219 | |----
220 | |$ ./command
221 | |Some output
222 | |----
223 | |'''.stripMargin()
224 |
225 | when:
226 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file)
227 |
228 | then:
229 | commands.size() == 1
230 | def command = commands.get(0)
231 | command.userInputs.size() == 3
232 | command.userInputs.get(0) == '1'
233 | command.userInputs.get(1) == ''
234 | command.userInputs.get(2) == 'yes'
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/samples-discovery/src/test/groovy/org/gradle/exemplar/loader/asciidoctor/AsciidoctorSamplesDiscoveryTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.loader.asciidoctor
17 |
18 | import org.asciidoctor.AttributesBuilder
19 | import org.asciidoctor.SafeMode
20 | import org.gradle.exemplar.model.Sample
21 | import org.junit.Rule
22 | import org.junit.rules.TemporaryFolder
23 | import spock.lang.Specification
24 |
25 | class AsciidoctorSamplesDiscoveryTest extends Specification {
26 | @Rule
27 | TemporaryFolder tmpDir = new TemporaryFolder()
28 |
29 | def "discovers samples inside an asciidoctor file with sources inline"() {
30 | given:
31 | def file = tmpDir.newFile('sample.adoc') << '''
32 | |= Document Title
33 | |
34 | |.Sample title
35 | |====
36 | |[.testable-sample]
37 | |=====
38 | |.hello.rb
39 | |[source,ruby]
40 | |----
41 | |target = "world"
42 | |puts "hello, #{target}"
43 | |----
44 | |
45 | |[.sample-command,allow-disordered-output=true]
46 | |----
47 | |$ ruby hello.rb
48 | |
49 | |hello, world
50 | |
51 | |some more output
52 | |----
53 | |=====
54 | |====
55 | |'''.stripMargin()
56 |
57 | when:
58 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file)
59 |
60 | then:
61 | samples.size() == 1
62 | def commands = samples.get(0).commands
63 |
64 | and:
65 | commands.size() == 1
66 | def command = commands.get(0)
67 | command.executable == 'ruby'
68 | command.args == ['hello.rb']
69 | command.allowDisorderedOutput
70 | command.expectedOutput == '''
71 | |hello, world
72 | |
73 | |some more output'''.stripMargin()
74 | }
75 |
76 | def "discovers samples inside an asciidoctor file with sources included"() {
77 | given:
78 | tmpDir.newFolder('src', 'samples', 'bash')
79 | tmpDir.newFile('src/samples/bash/script.sh') << '''
80 | |#!/usr/bin/env bash
81 | |
82 | |echo "Hello world"
83 | |'''.stripMargin()
84 | def file = tmpDir.newFile('sample.adoc') << '''
85 | |= Document title
86 | |
87 | |.Sample title
88 | |====
89 | |[.testable-sample,dir="src/samples/bash"]
90 | |=====
91 | |.script.sh
92 | |[source,bash]
93 | |----
94 | |include::src/samples/bash/script.sh[]
95 | |----
96 | |
97 | |[.sample-command]
98 | |----
99 | |$ bash script.sh
100 | |Hello world
101 | |----
102 | |=====
103 | |====
104 | |'''.stripMargin()
105 |
106 | when:
107 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file)
108 |
109 | then:
110 | samples.size() == 1
111 | samples.get(0).projectDir.toString() == 'src/samples/bash'
112 | def commands = samples.get(0).commands
113 |
114 | and:
115 | commands.size() == 1
116 | def command = commands.get(0)
117 | command.executable == 'bash'
118 | command.args == ['script.sh']
119 | command.expectedOutput == 'Hello world'
120 | }
121 |
122 | def "sample may include multiple sample-command blocks"() {
123 | given:
124 | def file = tmpDir.newFile('sample.adoc') << '''
125 | |= Document Title
126 | |
127 | |.Sample title
128 | |[.testable-sample]
129 | |====
130 | |
131 | |Run this first:
132 | |
133 | |[.sample-command]
134 | |----
135 | |$ ruby hello.rb
136 | |hello, world
137 | |----
138 | |
139 | |Then do this:
140 | |
141 | |[.sample-command]
142 | |----
143 | |$ mkdir some-dir
144 | |----
145 | |====
146 | |'''.stripMargin()
147 |
148 | when:
149 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file)
150 |
151 | then:
152 | samples.size() == 1
153 | def commands = samples.get(0).commands
154 |
155 | and:
156 | commands.size() == 2
157 | def command = commands.get(0)
158 | command.executable == 'ruby'
159 | command.args == ['hello.rb']
160 | command.expectedOutput == 'hello, world'
161 |
162 | def command2 = commands.get(1)
163 | command2.executable == 'mkdir'
164 | command2.args == ['some-dir']
165 | command2.expectedOutput.empty
166 | }
167 |
168 | def "sample-command block may include multiple commands"() {
169 | given:
170 | def file = tmpDir.newFile('sample.adoc') << '''
171 | |= Document Title
172 | |
173 | |.Sample title
174 | |[.testable-sample]
175 | |====
176 | |
177 | |Run this first:
178 | |
179 | |[.sample-command]
180 | |----
181 | |$ ruby hello.rb
182 | |
183 | |hello, world
184 | |
185 | |$ mkdir some-dir
186 | |$ cd some-dir
187 | |----
188 | |====
189 | |'''.stripMargin()
190 |
191 | when:
192 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file)
193 |
194 | then:
195 | samples.size() == 1
196 | def commands = samples.get(0).commands
197 |
198 | and:
199 | commands.size() == 3
200 | def command = commands.get(0)
201 | command.executable == "ruby"
202 | command.args == ["hello.rb"]
203 | command.expectedOutput == """
204 | |hello, world
205 | |""".stripMargin()
206 |
207 | def command2 = commands.get(1)
208 | command2.executable == 'mkdir'
209 | command2.args == ['some-dir']
210 | command2.expectedOutput.empty
211 |
212 | def command3 = commands.get(2)
213 | command3.executable == 'cd'
214 | command3.args == ['some-dir']
215 | command3.expectedOutput.empty
216 | }
217 |
218 | def "can include data from attributes"() {
219 | given:
220 | def file = tmpDir.newFile('sample.adoc') << '''
221 | |= Document Title
222 | |
223 | |.Sample title
224 | |[.testable-sample]
225 | |====
226 | |
227 | |Run this first:
228 | |
229 | |[listing.terminal.sample-command]
230 | |----
231 | |$ pwd
232 | |include::{sampleoutputdir}/pwd-output.txt[]
233 | |----
234 | |====
235 | |'''.stripMargin()
236 | def outputDir = tmpDir.newFolder('output')
237 | tmpDir.newFile('output/pwd-output.txt').text = "${tmpDir.root.getAbsolutePath()}\n"
238 |
239 | when:
240 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file) {
241 | it.attributes(AttributesBuilder.attributes().attribute('sampleoutputdir', outputDir.getAbsolutePath())).safe(SafeMode.UNSAFE)
242 | }
243 |
244 | then:
245 | samples.size() == 1
246 | def commands = samples.get(0).commands
247 |
248 | and:
249 | commands.size() == 1
250 | def command = commands.get(0)
251 | command.executable == 'pwd'
252 | command.args == []
253 | command.expectedOutput == tmpDir.root.getAbsolutePath()
254 | }
255 |
256 | def "can extract commands when using Asciidoctor callout"() {
257 | given:
258 | def file = tmpDir.newFile('sample.adoc') << '''
259 | |= Document Title
260 | |
261 | |[listing.terminal.testable-sample.sample-command]
262 | |----
263 | |$ ./command
264 | |Some output // <1>
265 | |----
266 | |<1> Some callout
267 | |'''.stripMargin()
268 |
269 | when:
270 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file)
271 |
272 | then:
273 | samples.size() == 1
274 | def commands = samples.get(0).commands
275 |
276 | and:
277 | commands.size() == 1
278 | def command = commands.get(0)
279 | command.executable == './command'
280 | command.args == []
281 | command.expectedOutput == 'Some output // <1>'
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/samples-discovery/src/test/groovy/org/gradle/exemplar/model/CommandsParserTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.gradle.exemplar.model
17 |
18 | import org.gradle.exemplar.InvalidSampleException
19 | import org.gradle.exemplar.loader.CommandsParser
20 | import org.junit.Rule
21 | import org.junit.rules.TemporaryFolder
22 | import spock.lang.Specification
23 |
24 | class CommandsParserTest extends Specification {
25 | @Rule
26 | TemporaryFolder tmpDir = new TemporaryFolder()
27 | File sampleConfigFile
28 |
29 | def setup() {
30 | sampleConfigFile = tmpDir.newFile("default.sample.conf")
31 | }
32 |
33 | def "fails fast when config file is badly formed"() {
34 | given:
35 | sampleConfigFile << "broken!"
36 |
37 | when:
38 | CommandsParser.parse(sampleConfigFile)
39 |
40 | then:
41 | def e = thrown(InvalidSampleException)
42 | e.message == "Could not read sample definition from ${sampleConfigFile}."
43 | e.cause != null
44 | }
45 |
46 | def "fails fast given no executable or commands array specified"() {
47 | given:
48 | sampleConfigFile << "bogus { unexpected: true }"
49 | tmpDir.newFile("bogus.sample.out").createNewFile()
50 |
51 | when:
52 | CommandsParser.parse(sampleConfigFile)
53 |
54 | then:
55 | def e = thrown(InvalidSampleException)
56 | e.message == "Could not read sample definition from ${sampleConfigFile}."
57 | e.cause.message == "A sample must be defined with an 'executable' or 'commands'"
58 | }
59 |
60 | def "fails fast when no executable for command specified"() {
61 | given:
62 | sampleConfigFile << """
63 | commands: [{ }, { }]
64 | """
65 |
66 | when:
67 | CommandsParser.parse(sampleConfigFile)
68 |
69 | then:
70 | def e = thrown(InvalidSampleException)
71 | e.message == "Could not read sample definition from ${sampleConfigFile}."
72 | e.cause.message == "'executable' field cannot be empty"
73 | }
74 |
75 | def "provides reasonable defaults for command config"() {
76 | given:
77 | sampleConfigFile << "executable: hello"
78 |
79 | when:
80 | List commands = CommandsParser.parse(sampleConfigFile)
81 | Command command = commands[0]
82 |
83 | then:
84 | commands.size() == 1
85 | command.executable == "hello"
86 | command.executionSubdirectory == null
87 | command.args == []
88 | command.flags == []
89 | !command.expectFailure
90 | command.expectedOutput == null
91 | !command.allowAdditionalOutput
92 | !command.allowDisorderedOutput
93 | }
94 |
95 | def "loads custom values for all command config options"() {
96 | given:
97 | sampleConfigFile << """
98 | executable: gradle
99 | execution-subdirectory: subproj
100 | args: build
101 | flags: -I init.gradle.kts
102 | expect-failure: true
103 | allow-additional-output: true
104 | allow-disordered-output: true
105 | expected-output-file: customLoggerKts.out
106 | """
107 | File expectedOutputFile = tmpDir.newFile("customLoggerKts.out")
108 | expectedOutputFile << "> Task :build"
109 |
110 | when:
111 | List commands = CommandsParser.parse(sampleConfigFile)
112 | Command command = commands[0]
113 |
114 | then:
115 | commands.size() == 1
116 | command.executable == "gradle"
117 | command.executionSubdirectory == "subproj"
118 | command.args == ["build"]
119 | command.flags == ["-I", "init.gradle.kts"]
120 | command.expectFailure
121 | command.expectedOutput == "> Task :build"
122 | command.allowAdditionalOutput
123 | command.allowDisorderedOutput
124 | }
125 |
126 | def "parses multiple steps"() {
127 | given:
128 | sampleConfigFile << """
129 | commands: [{
130 | executable: gradle
131 | execution-subdirectory: subproj
132 | args: produce
133 | },{
134 | executable: gradle
135 | execution-subdirectory: otherproject
136 | args: consume
137 | flags: --quiet
138 | expect-failure: true
139 | allow-additional-output: true
140 | allow-disordered-output: true
141 | expected-output-file: customLoggerKts.out
142 | }]
143 | """
144 | File expectedOutputFile = tmpDir.newFile("customLoggerKts.out")
145 | expectedOutputFile << "> Task :build"
146 |
147 | when:
148 | List commands = CommandsParser.parse(sampleConfigFile)
149 |
150 | then:
151 | commands.size() == 2
152 |
153 | Command firstCommand = commands[0]
154 | firstCommand.executable == "gradle"
155 | firstCommand.executionSubdirectory == "subproj"
156 | firstCommand.args == ["produce"]
157 | !firstCommand.expectFailure
158 |
159 | Command secondCommand = commands[1]
160 | secondCommand.executable == "gradle"
161 | secondCommand.executionSubdirectory == "otherproject"
162 | secondCommand.args == ["consume"]
163 | secondCommand.flags == ["--quiet"]
164 | secondCommand.expectFailure
165 | secondCommand.expectedOutput == "> Task :build"
166 | secondCommand.allowAdditionalOutput
167 | secondCommand.allowDisorderedOutput
168 | }
169 |
170 | def "loads included config"() {
171 | given:
172 | File normalizersConfigFile = tmpDir.newFile("normalizers.conf")
173 | normalizersConfigFile << """
174 | flags: --init-script foo.bar.kts
175 | """
176 |
177 | sampleConfigFile << """
178 | executable: gradle
179 | args: build
180 | include file("${normalizersConfigFile.absolutePath.replace((char) '\\', (char) '/')}")
181 | """
182 | tmpDir.newFile("default.sample.out").createNewFile()
183 |
184 | when:
185 | List commands = CommandsParser.parse(sampleConfigFile)
186 | Command command = commands[0]
187 |
188 | then:
189 | commands.size() == 1
190 | command.executable == "gradle"
191 | command.args == ["build"]
192 | command.flags == ["--init-script", "foo.bar.kts"]
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.gradle.develocity") version "4.0.1"
3 | id("io.github.gradle.gradle-enterprise-conventions-plugin") version "0.10.3"
4 | }
5 |
6 | rootProject.name = "exemplar"
7 |
8 | include("samples-discovery")
9 | include("samples-check")
10 | include("docs")
11 |
--------------------------------------------------------------------------------