You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2021/08/05 14:59:32 UTC
[sling-org-apache-sling-graphql-schema-aggregator] branch master
updated: SLING-10680 - Add support for versioning partials
This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-schema-aggregator.git
The following commit(s) were added to refs/heads/master by this push:
new a3c54a4 SLING-10680 - Add support for versioning partials
a3c54a4 is described below
commit a3c54a4f98dac0266360ce5b860ca96c75516af0
Author: Radu Cotescu <17...@users.noreply.github.com>
AuthorDate: Thu Aug 5 16:59:28 2021 +0200
SLING-10680 - Add support for versioning partials
* partials can optionally provide a version, which can now also be used for REQUIRES
---
pom.xml | 6 ++++
.../schema/aggregator/impl/BundleEntryPartial.java | 2 +-
.../aggregator/impl/DefaultSchemaAggregator.java | 36 +++++++++++---------
.../graphql/schema/aggregator/impl/Partial.java | 30 ++++++++++++++---
.../schema/aggregator/impl/PartialInfo.java | 37 +++++++++++++++++++-
.../schema/aggregator/impl/PartialReader.java | 39 ++++++++++++----------
.../aggregator/impl/ProviderBundleTracker.java | 10 +++---
.../impl/DefaultSchemaAggregatorTest.java | 38 +++++++++++++++------
.../schema/aggregator/impl/PartialReaderTest.java | 9 +++++
.../aggregator/impl/ProviderBundleTrackerTest.java | 2 +-
.../aggregator/it/SchemaAggregatorTestSupport.java | 3 +-
src/test/resources/partials/required-1.0.0.txt | 4 +++
src/test/resources/partials/versioned-1.0.0.txt | 3 ++
src/test/resources/partials/versioned-2.0.0.txt | 3 ++
.../partials/versionedPartials-output.txt | 10 ++++++
15 files changed, 176 insertions(+), 56 deletions(-)
diff --git a/pom.xml b/pom.xml
index cd56bb3..e216214 100644
--- a/pom.xml
+++ b/pom.xml
@@ -175,6 +175,12 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.15</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
diff --git a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/BundleEntryPartial.java b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/BundleEntryPartial.java
index 3207e0a..a033db5 100644
--- a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/BundleEntryPartial.java
+++ b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/BundleEntryPartial.java
@@ -74,6 +74,6 @@ class BundleEntryPartial extends PartialReader implements Comparable<BundleEntry
@Override
public int compareTo(BundleEntryPartial o) {
- return getName().compareTo(o.getName());
+ return getPartialInfo().compareTo(o.getPartialInfo());
}
}
diff --git a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregator.java b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregator.java
index 6f8a8dd..c3e58c4 100644
--- a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregator.java
+++ b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregator.java
@@ -49,7 +49,7 @@ public class DefaultSchemaAggregator implements SchemaAggregator {
NO_BLOCK,
WITH_BLOCK_IF_NOT_EMPTY,
WITH_BLOCK
- };
+ }
@Reference
private ProviderBundleTracker tracker;
@@ -91,7 +91,7 @@ public class DefaultSchemaAggregator implements SchemaAggregator {
}
private void writeSourceInfo(Writer target, Partial p) throws IOException {
- target.write(String.format("%n# %s.source=%s%n", getClass().getSimpleName(), p.getName()));
+ target.write(String.format("%n# %s.source=%s%n", getClass().getSimpleName(), p.getPartialInfo()));
}
@Override
@@ -100,7 +100,7 @@ public class DefaultSchemaAggregator implements SchemaAggregator {
target.write(String.format("# %s", info));
// build list of selected providers
- final Map<String, Partial> providers = tracker.getSchemaProviders();
+ final Map<PartialInfo, Partial> providers = tracker.getSchemaProviders();
if(log.isDebugEnabled()) {
log.debug("Aggregating schemas, request={}, providers={}", Arrays.asList(providerNamesOrRegexp), providers.keySet());
}
@@ -123,51 +123,57 @@ public class DefaultSchemaAggregator implements SchemaAggregator {
if(partialNames.length() > 0) {
partialNames.append(",");
}
- partialNames.append(p.getName());
+ partialNames.append(p.getPartialInfo());
});
target.write(String.format("%n# End of Schema aggregated from {%s} by %s", partialNames, getClass().getSimpleName()));
}
- Set<Partial> selectProviders(Map<String, Partial> providers, Set<String> missing, String ... providerNamesOrRegexp) {
+ Set<Partial> selectProviders(Map<PartialInfo, Partial> providers, Set<String> missing, String ... providerNamesOrRegexp) {
final Set<Partial> result= new LinkedHashSet<>();
for(String str : providerNamesOrRegexp) {
final Pattern p = toRegexp(str);
if(p != null) {
log.debug("Selecting providers matching {}", p);
providers.entrySet().stream()
- .filter(e -> p.matcher(e.getKey()).matches())
- .sorted(Comparator.comparing(e -> e.getValue().getName()))
+ .filter(e -> p.matcher(e.getKey().getName()).matches())
+ .sorted(Comparator.comparing(e -> e.getValue().getPartialInfo()))
.forEach(e -> addWithRequirements(providers, result, missing, e.getValue(), 0))
;
} else {
log.debug("Selecting provider with key={}", str);
- final Partial psp = providers.get(str);
- if(psp == null) {
+ Optional<PartialInfo> fromString = PartialInfo.fromRequiresSection(str).stream().findFirst();
+ if (fromString.isPresent()) {
+ PartialInfo selected = fromString.get();
+ final Partial psp = providers.get(selected);
+ if (psp == null) {
+ missing.add(str);
+ continue;
+ }
+ addWithRequirements(providers, result, missing, psp, 0);
+ } else {
missing.add(str);
- continue;
}
- addWithRequirements(providers, result, missing, psp, 0);
}
}
return result;
}
- private void addWithRequirements(Map<String, Partial> providers, Set<Partial> addTo, Set<String> missing, Partial p, int recursionLevel) {
+ private void addWithRequirements(Map<PartialInfo, Partial> providers, Set<Partial> addTo, Set<String> missing, Partial p, int recursionLevel) {
// simplistic cycle detection
if(recursionLevel > MAX_REQUIREMENTS_RECURSION_LEVEL) {
throw new RuntimeException(String.format(
"Requirements depth over %d, requirements cycle suspected at partial %s",
MAX_REQUIREMENTS_RECURSION_LEVEL,
- p.getName()
+ p.getPartialInfo()
));
}
addTo.add(p);
- for(String req : p.getRequiredPartialNames()) {
+ for(PartialInfo req : p.getRequiredPartialNames()) {
final Partial preq = providers.get(req);
if(preq == null) {
- missing.add(req);
+ missing.add(req.toString());
} else {
addWithRequirements(providers, addTo, missing, preq, recursionLevel + 1);
}
diff --git a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/Partial.java b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/Partial.java
index 9e14d35..f0268a9 100644
--- a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/Partial.java
+++ b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/Partial.java
@@ -24,7 +24,6 @@ import java.util.Optional;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
-import org.osgi.framework.Version;
/** Wrapper for the partials format, that parses a partial file and
* provides access to its sections.
@@ -48,12 +47,35 @@ public interface Partial {
TYPES
}
- /** The name of this partial */
- @NotNull String getName();
+ /**
+ * Returns the partial info.
+ *
+ * @return the partial info
+ */
+ @NotNull PartialInfo getPartialInfo();
/** Return a specific section of the partial, by name */
@NotNull Optional<Section> getSection(SectionName name);
/** Names of the Partials on which this one depends */
- @NotNull Set<String> getRequiredPartialNames();
+ @NotNull Set<PartialInfo> getRequiredPartialNames();
+
+ /**
+ * <p>
+ * Returns the digest of the source that was used to build this partial. Implementations should output this using the following format:
+ * <pre>
+ * algorithm: digest
+ * </pre>
+ * where the algorithm has to be one of the standard names defined in the
+ * <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#messagedigest-algorithms">Java Security Standard Algorithm Names</a>.
+ * </p>
+ * <p>A SHA-256 digest would have, for example, the following format:
+ * <pre>
+ * SHA-256: 703bd06e9d65118c75abe9a7a06f6a2fcdb8a19ef62d994f4cc1be0b34420383
+ * </pre>
+ * </p>
+ * @return the digest of the source that was used to build this partial
+ */
+ @NotNull String getDigest();
+
}
diff --git a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/PartialInfo.java b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/PartialInfo.java
index 6b18bee..1ddf71c 100644
--- a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/PartialInfo.java
+++ b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/PartialInfo.java
@@ -22,6 +22,7 @@ import java.net.URL;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -32,7 +33,7 @@ import org.osgi.framework.Version;
/**
* This class provides some utility methods to extract information about a partial without parsing it.
*/
-public final class PartialInfo {
+public final class PartialInfo implements Comparable<PartialInfo> {
private static final String PARTIAL_NAME_AND_VERSION_REGEX = "([a-z][a-zA-Z0-9_\\.]*)(-(\\d\\.\\d\\.\\d))?";
@@ -72,6 +73,40 @@ public final class PartialInfo {
return version;
}
+ @Override
+ public int compareTo(@NotNull PartialInfo o) {
+ if (this.equals(o)) {
+ return 0;
+ }
+ int nameComparison = this.name.compareTo(o.name);
+ if (nameComparison == 0) {
+ return version.compareTo(o.version);
+ }
+ return nameComparison;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, version);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof PartialInfo) {
+ PartialInfo other = (PartialInfo) obj;
+ return Objects.equals(name, other.name) && Objects.equals(version, other.version);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return name + (version == Version.emptyVersion ? "" : "-" + version);
+ }
+
/**
* Parses a {@code path} and returns a {@link PartialInfo}.
*
diff --git a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/PartialReader.java b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/PartialReader.java
index 54f13e5..b898317 100644
--- a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/PartialReader.java
+++ b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/PartialReader.java
@@ -20,17 +20,19 @@ package org.apache.sling.graphql.schema.aggregator.impl;
import java.io.IOException;
import java.io.Reader;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.EnumMap;
-import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.stream.Stream;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedReader;
import org.jetbrains.annotations.NotNull;
@@ -44,8 +46,9 @@ public class PartialReader implements Partial {
private static final int EOL = '\n';
private final Map<SectionName, Section> sections = new EnumMap<>(SectionName.class);
- private final String name;
- private final Set<String> requiredPartialNames;
+ private final PartialInfo partialInfo;
+ private final Set<PartialInfo> requiredPartialNames;
+ private final String digest;
/** The PARTIAL section is the only required one */
public static final String PARTIAL_SECTION = "PARTIAL";
@@ -90,20 +93,15 @@ public class PartialReader implements Partial {
}
public PartialReader(@NotNull PartialInfo partialInfo, @NotNull Supplier<Reader> source) throws IOException {
- this.name = partialInfo.getName();
+ this.partialInfo = partialInfo;
parse(source);
+ this.digest = "SHA-256: " + Hex.encodeHexString(
+ DigestUtils.updateDigest(DigestUtils.getSha256Digest(), IOUtils.toByteArray(source.get(), StandardCharsets.UTF_8)).digest());
final Partial.Section requirements = sections.get(SectionName.REQUIRES);
if(requirements == null) {
requiredPartialNames = Collections.emptySet();
} else {
- requiredPartialNames = new HashSet<>();
- Stream.of(
- requirements.getDescription().split(",")
- )
- .map(String::trim)
- .filter(s -> !s.isEmpty())
- .forEach(requiredPartialNames::add)
- ;
+ requiredPartialNames = PartialInfo.fromRequiresSection(requirements.getDescription());
}
}
@@ -168,18 +166,23 @@ public class PartialReader implements Partial {
}
@Override
+ public @NotNull PartialInfo getPartialInfo() {
+ return partialInfo;
+ }
+
+ @Override
public @NotNull Optional<Section> getSection(Partial.SectionName name) {
final Section s = sections.get(name);
return Optional.ofNullable(s);
}
@Override
- public @NotNull String getName() {
- return name;
+ public @NotNull Set<PartialInfo> getRequiredPartialNames() {
+ return Collections.unmodifiableSet(requiredPartialNames);
}
@Override
- public @NotNull Set<String> getRequiredPartialNames() {
- return requiredPartialNames;
+ public @NotNull String getDigest() {
+ return digest;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTracker.java b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTracker.java
index 09c7b31..e016ae1 100644
--- a/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTracker.java
+++ b/src/main/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTracker.java
@@ -55,7 +55,7 @@ public class ProviderBundleTracker implements BundleTrackerCustomizer<Object> {
public static final String SCHEMA_PATH_HEADER = "Sling-GraphQL-Schema";
private final Logger log = LoggerFactory.getLogger(getClass().getName());
- private final Map<String, BundleEntryPartial> schemaProviders = new ConcurrentHashMap<>();
+ private final Map<PartialInfo, BundleEntryPartial> schemaProviders = new ConcurrentHashMap<>();
private BundleContext bundleContext;
@@ -96,11 +96,11 @@ public class ProviderBundleTracker implements BundleTrackerCustomizer<Object> {
private void addIfNotPresent(BundleEntryPartial a) {
if(a != null) {
- if(schemaProviders.containsKey(a.getName())) {
- log.warn("Partial provider with name {} already present, new one will be ignored", a.getName());
+ if(schemaProviders.containsKey(a.getPartialInfo())) {
+ log.warn("Partial provider for partial {} already present, new one will be ignored", a.getPartialInfo());
} else {
log.info("Registering {}", a);
- schemaProviders.put(a.getName(), a);
+ schemaProviders.put(a.getPartialInfo(), a);
}
}
}
@@ -121,7 +121,7 @@ public class ProviderBundleTracker implements BundleTrackerCustomizer<Object> {
// do nothing
}
- Map<String, Partial> getSchemaProviders() {
+ Map<PartialInfo, Partial> getSchemaProviders() {
return Collections.unmodifiableMap(schemaProviders);
}
}
diff --git a/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregatorTest.java b/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregatorTest.java
index c0268eb..005274d 100644
--- a/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregatorTest.java
+++ b/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/DefaultSchemaAggregatorTest.java
@@ -18,11 +18,6 @@
*/
package org.apache.sling.graphql.schema.aggregator.impl;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
@@ -30,10 +25,6 @@ import java.lang.reflect.Field;
import java.util.Optional;
import java.util.stream.Stream;
-import graphql.language.TypeDefinition;
-import graphql.schema.idl.SchemaParser;
-import graphql.schema.idl.TypeDefinitionRegistry;
-
import org.apache.commons.io.IOUtils;
import org.apache.sling.graphql.schema.aggregator.U;
import org.junit.Before;
@@ -41,6 +32,14 @@ import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
+import graphql.language.TypeDefinition;
+import graphql.schema.idl.SchemaParser;
+import graphql.schema.idl.TypeDefinitionRegistry;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -74,7 +73,7 @@ public class DefaultSchemaAggregatorTest {
}
@Test
- public void noProviders() throws Exception{
+ public void noProviders() {
final StringWriter target = new StringWriter();
final IOException iox = assertThrows(IOException.class, () -> dsa.aggregate(target, "Aprov", "Bprov"));
assertContainsIgnoreCase("missing providers", iox.getMessage());
@@ -175,6 +174,25 @@ public class DefaultSchemaAggregatorTest {
}
@Test
+ public void versionedPartials() throws IOException {
+ final StringWriter target = new StringWriter();
+ tracker.addingBundle(U.mockProviderBundle(bundleContext, "required.partials", 1, "required-1.0.0.txt"), null);
+ tracker.addingBundle(U.mockProviderBundle(bundleContext, "versioned.partials", 2, "versioned-1.0.0.txt"), null);
+ dsa.aggregate(target, "versioned-1.0.0");
+ assertOutput("/partials/versionedPartials-output.txt", target.toString());
+ }
+
+ @Test
+ public void versionedPartialsMissingCorrectVersion() throws IOException {
+ final StringWriter target = new StringWriter();
+ tracker.addingBundle(U.mockProviderBundle(bundleContext, "required.partials", 1, "required-1.0.0.txt"), null);
+ tracker.addingBundle(U.mockProviderBundle(bundleContext, "versioned.partials", 2, "versioned-2.0.0.txt"), null);
+ final IOException iox = assertThrows(IOException.class, () -> dsa.aggregate(target, "versioned-2.0.0"));
+ assertContainsIgnoreCase("Missing providers", iox.getMessage());
+ assertContainsIgnoreCase("required-2.0.0", iox.getMessage());
+ }
+
+ @Test
public void cycleInRequirements() throws Exception {
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "SDL", 1, "circularA.txt", "circularB.txt"), null);
diff --git a/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/PartialReaderTest.java b/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/PartialReaderTest.java
index 6e031f5..615fc3e 100644
--- a/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/PartialReaderTest.java
+++ b/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/PartialReaderTest.java
@@ -148,4 +148,13 @@ public class PartialReaderTest {
assertTrue("Expecting requires section", p.getSection(Partial.SectionName.REQUIRES).isPresent());
assertEquals("[a.sdl, b.sdl]", p.getRequiredPartialNames().toString());
}
+
+ @Test
+ public void testDigest() throws IOException {
+ final PartialReader p = new PartialReader(
+ PartialInfo.fromPath(Paths.get("/partials/versioned-1.0.0.txt")),
+ getResourceReaderSupplier("/partials/versioned-1.0.0.txt")
+ );
+ assertEquals("SHA-256: 703bd06e9d65118c75abe9a7a06f6a2fcdb8a19ef62d994f4cc1be0b34420383", p.getDigest());
+ }
}
diff --git a/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTrackerTest.java b/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTrackerTest.java
index 2ceee73..d8a57c4 100644
--- a/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTrackerTest.java
+++ b/src/test/java/org/apache/sling/graphql/schema/aggregator/impl/ProviderBundleTrackerTest.java
@@ -83,7 +83,7 @@ public class ProviderBundleTrackerTest {
final Bundle b = U.mockProviderBundle(bundleContext, "B", ++bundleId, "tt.txt", "another.x");
tracker.addingBundle(a, null);
tracker.addingBundle(b, null);
- capture.assertContains(Level.WARN, "Partial provider with name tt already present");
+ capture.assertContains(Level.WARN, "Partial provider for partial tt already present");
assertEquals(2, tracker.getSchemaProviders().size());
}
diff --git a/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorTestSupport.java b/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorTestSupport.java
index 8163c41..72b02c2 100644
--- a/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorTestSupport.java
+++ b/src/test/java/org/apache/sling/graphql/schema/aggregator/it/SchemaAggregatorTestSupport.java
@@ -85,6 +85,7 @@ public abstract class SchemaAggregatorTestSupport extends TestSupport {
.put("whitelist.bundles.regexp", "^PAXEXAM.*$")
.asOption(),
mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.servlet-helpers").versionAsInProject(),
+ mavenBundle().groupId("commons-codec").artifactId("commons-codec").versionAsInProject(),
junitBundles()
);
}
@@ -147,4 +148,4 @@ public abstract class SchemaAggregatorTestSupport extends TestSupport {
protected String getContent(String path) throws Exception {
return executeRequest("GET", path, null, null, null, 200).getOutputAsString();
}
-}
\ No newline at end of file
+}
diff --git a/src/test/resources/partials/required-1.0.0.txt b/src/test/resources/partials/required-1.0.0.txt
new file mode 100644
index 0000000..8e4ecef
--- /dev/null
+++ b/src/test/resources/partials/required-1.0.0.txt
@@ -0,0 +1,4 @@
+PARTIAL: A partial that will be required by another.
+
+PROLOGUE:
+This section should be present at the top of the generated schema file.
diff --git a/src/test/resources/partials/versioned-1.0.0.txt b/src/test/resources/partials/versioned-1.0.0.txt
new file mode 100644
index 0000000..5bf71b7
--- /dev/null
+++ b/src/test/resources/partials/versioned-1.0.0.txt
@@ -0,0 +1,3 @@
+PARTIAL: Testing a versioned partial
+
+REQUIRES: required-1.0.0
diff --git a/src/test/resources/partials/versioned-2.0.0.txt b/src/test/resources/partials/versioned-2.0.0.txt
new file mode 100644
index 0000000..e2e5866
--- /dev/null
+++ b/src/test/resources/partials/versioned-2.0.0.txt
@@ -0,0 +1,3 @@
+PARTIAL: Testing a versioned partial
+
+REQUIRES: required-2.0.0
diff --git a/src/test/resources/partials/versionedPartials-output.txt b/src/test/resources/partials/versionedPartials-output.txt
new file mode 100644
index 0000000..bf642f0
--- /dev/null
+++ b/src/test/resources/partials/versionedPartials-output.txt
@@ -0,0 +1,10 @@
+# Schema aggregated by DefaultSchemaAggregator
+
+# DefaultSchemaAggregator.source=required-1.0.0
+This section should be present at the top of the generated schema file.
+
+type Query {
+
+}
+
+# End of Schema aggregated from {versioned-1.0.0,required-1.0.0} by DefaultSchemaAggregator