##// END OF EJS Templates
variants, variantSources tests
cin -
r45:783f552a9ada default
parent child
Show More
@@ -0,0 +1,83
1 package org.implab.gradle.variants;
2
3 import static org.junit.jupiter.api.Assertions.assertTrue;
4
5 import java.io.File;
6 import java.io.IOException;
7 import java.nio.file.Files;
8 import java.nio.file.Path;
9 import java.util.LinkedHashSet;
10 import java.util.List;
11 import java.util.stream.Collectors;
12
13 import org.gradle.testkit.runner.GradleRunner;
14 import org.gradle.testkit.runner.UnexpectedBuildFailure;
15 import org.implab.gradle.common.sources.GenericSourceSet;
16 import org.junit.jupiter.api.io.TempDir;
17
18 abstract class AbstractFunctionalTest {
19 private static final String SETTINGS_FILE = "settings.gradle";
20 private static final String BUILD_FILE = "build.gradle";
21
22 @TempDir
23 Path testProjectDir;
24
25 protected void writeSettings(String rootProjectName) throws IOException {
26 writeFile(SETTINGS_FILE, "rootProject.name = '" + rootProjectName + "'\n");
27 }
28
29 protected void writeBuildFile(String body) throws IOException {
30 writeFile(BUILD_FILE, buildscriptClasspathBlock() + "\n" + body);
31 }
32
33 protected GradleRunner runner(String... arguments) {
34 return GradleRunner.create()
35 .withProjectDir(testProjectDir.toFile())
36 .withArguments(arguments)
37 .forwardOutput();
38 }
39
40 protected void assertBuildFails(String expectedError, String... arguments) {
41 var ex = org.junit.jupiter.api.Assertions.assertThrows(
42 UnexpectedBuildFailure.class,
43 () -> runner(arguments).build());
44
45 var output = ex.getBuildResult().getOutput();
46 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
47 }
48
49 private static String buildscriptClasspathBlock() {
50 var classpath = pluginClasspath().stream()
51 .map(file -> "'" + file.getAbsolutePath().replace("\\", "\\\\") + "'")
52 .collect(Collectors.joining(", "));
53
54 return """
55 buildscript {
56 dependencies {
57 classpath files(%s)
58 }
59 }
60 """.formatted(classpath);
61 }
62
63 private static List<File> pluginClasspath() {
64 try {
65 var files = new LinkedHashSet<File>();
66 files.add(codeSource(VariantSourcesPlugin.class));
67 files.add(codeSource(GenericSourceSet.class));
68 return List.copyOf(files);
69 } catch (Exception e) {
70 throw new RuntimeException("Unable to build plugin classpath for test", e);
71 }
72 }
73
74 private static File codeSource(Class<?> type) throws Exception {
75 return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI()).toFile();
76 }
77
78 private void writeFile(String relativePath, String content) throws IOException {
79 Path path = testProjectDir.resolve(relativePath);
80 Files.createDirectories(path.getParent());
81 Files.writeString(path, content);
82 }
83 }
@@ -0,0 +1,431
1 package org.implab.gradle.variants;
2
3 import static org.junit.jupiter.api.Assertions.assertTrue;
4
5 import org.gradle.testkit.runner.BuildResult;
6 import org.junit.jupiter.api.Test;
7
8 class VariantSourcesPluginFunctionalTest extends AbstractFunctionalTest {
9
10 @Test
11 void exposesDerivedViewsAndStableSourceSetProvider() throws Exception {
12 writeSettings("variant-sources-derived-views");
13 writeBuildFile("""
14 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
15
16 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
17 variantsExt.layers.create('main')
18 variantsExt.layers.create('test')
19 variantsExt.roles.create('production')
20 variantsExt.roles.create('test')
21
22 variantsExt.variant('browser') {
23 role('production') { layers('main') }
24 role('test') { layers('main', 'test') }
25 }
26
27 def lines = []
28
29 variantSources.whenFinalized { ctx ->
30 lines << "units=" + ctx.compileUnits.units
31 .collect { "${it.variant().name}:${it.layer().name}" }
32 .sort()
33 .join(',')
34
35 def browser = ctx.variants.variants.find { it.name == 'browser' }
36 def production = ctx.variants.roles.find { it.name == 'production' }
37 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
38 def projection = ctx.roleProjections.getProjection(browser, production)
39 def unit = ctx.compileUnits.getUnit(browser, mainLayer)
40
41 def left = ctx.sourceSets.getSourceSet(unit)
42 def right = ctx.sourceSets.getSourceSet(unit)
43
44 lines << "projectionUnits=" + ctx.roleProjections.getUnits(projection)
45 .collect { it.layer().name }
46 .sort()
47 .join(',')
48 lines << "mainSourceSet=" + left.name
49 lines << "sameProvider=" + left.is(right)
50 }
51
52 afterEvaluate {
53 variantSources.whenFinalized { ctx ->
54 lines << "late:variants=" + ctx.variants.variants.collect { it.name }.sort().join(',')
55 }
56 }
57
58 tasks.register('probe') {
59 doLast {
60 lines.each { println(it) }
61 }
62 }
63 """);
64
65 BuildResult result = runner("probe").build();
66
67 assertTrue(result.getOutput().contains("units=browser:main,browser:test"));
68 assertTrue(result.getOutput().contains("projectionUnits=main"));
69 assertTrue(result.getOutput().contains("mainSourceSet=browserMain"));
70 assertTrue(result.getOutput().contains("sameProvider=true"));
71 assertTrue(result.getOutput().contains("late:variants=browser"));
72 }
73
74 @Test
75 void appliesSelectorPrecedenceForFutureMaterialization() throws Exception {
76 writeSettings("variant-sources-precedence");
77 writeBuildFile("""
78 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
79
80 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
81 variantsExt.layers.create('main')
82 variantsExt.layers.create('test')
83 variantsExt.roles.create('production')
84 variantsExt.roles.create('test')
85
86 variantsExt.variant('browser') {
87 role('production') { layers('main') }
88 role('test') { layers('main', 'test') }
89 }
90
91 variantsExt.variant('node') {
92 role('production') { layers('main') }
93 }
94
95 def events = []
96
97 variantSources {
98 variant('browser') {
99 events << "variant:" + name
100 }
101 layer('main') {
102 events << "layer:" + name
103 }
104 unit('browser', 'main') {
105 events << "unit:" + name
106 }
107 }
108
109 afterEvaluate {
110 variantSources.whenFinalized { ctx ->
111 def browser = ctx.variants.variants.find { it.name == 'browser' }
112 def node = ctx.variants.variants.find { it.name == 'node' }
113 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
114 def testLayer = ctx.variants.layers.find { it.name == 'test' }
115
116 def browserMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.getUnit(browser, mainLayer)).get()
117 def browserTest = ctx.sourceSets.getSourceSet(ctx.compileUnits.getUnit(browser, testLayer)).get()
118 def nodeMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.getUnit(node, mainLayer)).get()
119 def bySourceSet = events.groupBy { it.split(':', 2)[1] }
120
121 println("browserMain=" + bySourceSet[browserMain.name].collect { it.split(':', 2)[0] }.join(','))
122 println("browserTest=" + bySourceSet[browserTest.name].collect { it.split(':', 2)[0] }.join(','))
123 println("nodeMain=" + bySourceSet[nodeMain.name].collect { it.split(':', 2)[0] }.join(','))
124 }
125 }
126 """);
127
128 BuildResult result = runner("help").build();
129
130 assertTrue(result.getOutput().contains("browserMain=variant,layer,unit"));
131 assertTrue(result.getOutput().contains("browserTest=variant"));
132 assertTrue(result.getOutput().contains("nodeMain=layer"));
133 }
134
135 @Test
136 void failsLateConfigurationByDefaultAfterMaterialization() throws Exception {
137 writeSettings("variant-sources-late-fail");
138 writeBuildFile("""
139 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
140
141 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
142 variantsExt.layers.create('main')
143 variantsExt.roles.create('production')
144 variantsExt.variant('browser') {
145 role('production') { layers('main') }
146 }
147
148 afterEvaluate {
149 variantSources.whenFinalized { ctx ->
150 def browser = ctx.variants.variants.find { it.name == 'browser' }
151 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
152 def unit = ctx.compileUnits.getUnit(browser, mainLayer)
153
154 ctx.sourceSets.getSourceSet(unit).get()
155 variantSources.layer('main') {
156 declareOutputs('late')
157 }
158 }
159 }
160 """);
161
162 assertBuildFails("Source sets for [layer=main] layer already materialized", "help");
163 }
164
165 @Test
166 void allowsLateConfigurationWhenSelectedBeforeFirstSelector() throws Exception {
167 writeSettings("variant-sources-late-allow");
168 writeBuildFile("""
169 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
170
171 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
172 variantsExt.layers.create('main')
173 variantsExt.roles.create('production')
174 variantsExt.variant('browser') {
175 role('production') { layers('main') }
176 }
177
178 variantSources {
179 lateConfigurationPolicy {
180 allowLateConfiguration()
181 }
182 }
183
184 afterEvaluate {
185 variantSources.whenFinalized { ctx ->
186 def browser = ctx.variants.variants.find { it.name == 'browser' }
187 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
188 def unit = ctx.compileUnits.getUnit(browser, mainLayer)
189
190 def sourceSet = ctx.sourceSets.getSourceSet(unit).get()
191 variantSources.layer('main') {
192 declareOutputs('late')
193 }
194 sourceSet.output('late')
195 println('lateAllowed=ok')
196 }
197 }
198 """);
199
200 BuildResult result = runner("help").build();
201 assertTrue(result.getOutput().contains("lateAllowed=ok"));
202 }
203
204 @Test
205 void warnsAndAppliesLateConfigurationWhenWarnModeSelected() throws Exception {
206 writeSettings("variant-sources-late-warn");
207 writeBuildFile("""
208 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
209
210 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
211 variantsExt.layers.create('main')
212 variantsExt.roles.create('production')
213 variantsExt.variant('browser') {
214 role('production') { layers('main') }
215 }
216
217 variantSources {
218 lateConfigurationPolicy {
219 warnOnLateConfiguration()
220 }
221 }
222
223 afterEvaluate {
224 variantSources.whenFinalized { ctx ->
225 def browser = ctx.variants.variants.find { it.name == 'browser' }
226 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
227 def unit = ctx.compileUnits.getUnit(browser, mainLayer)
228
229 def sourceSet = ctx.sourceSets.getSourceSet(unit).get()
230 variantSources.layer('main') {
231 declareOutputs('late')
232 }
233 sourceSet.output('late')
234 println('lateWarn=ok')
235 }
236 }
237 """);
238
239 BuildResult result = runner("help").build();
240
241 assertTrue(result.getOutput().contains("Source sets for [layer=main] layer already materialized"));
242 assertTrue(result.getOutput().contains("lateWarn=ok"));
243 }
244
245 @Test
246 void rejectsChangingLateConfigurationPolicyAfterFirstSelector() throws Exception {
247 writeSettings("variant-sources-late-policy-fixed");
248 writeBuildFile("""
249 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
250
251 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
252 variantsExt.layers.create('main')
253 variantsExt.roles.create('production')
254 variantsExt.variant('browser') {
255 role('production') { layers('main') }
256 }
257
258 variantSources {
259 variant('browser') {
260 declareOutputs('js')
261 }
262 lateConfigurationPolicy {
263 allowLateConfiguration()
264 }
265 }
266 """);
267
268 assertBuildFails("Lazy configuration policy already applied", "help");
269 }
270
271 @Test
272 void failsOnProjectedNameCollisionByDefault() throws Exception {
273 writeSettings("variant-sources-name-collision-fail");
274 writeBuildFile("""
275 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
276
277 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
278 variantsExt.layers.create('variantBar')
279 variantsExt.layers.create('bar')
280 variantsExt.roles.create('production')
281
282 variantsExt.variant('foo') {
283 role('production') { layers('variantBar') }
284 }
285 variantsExt.variant('fooVariant') {
286 role('production') { layers('bar') }
287 }
288 """);
289
290 assertBuildFails("The same source set names are produced by different compile units", "help");
291 }
292
293 @Test
294 void resolvesProjectedNameCollisionDeterministicallyWhenConfigured() throws Exception {
295 writeSettings("variant-sources-name-collision-resolve");
296 writeBuildFile("""
297 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
298
299 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
300 variantsExt.layers.create('variantBar')
301 variantsExt.layers.create('bar')
302 variantsExt.roles.create('production')
303
304 variantsExt.variant('foo') {
305 role('production') { layers('variantBar') }
306 }
307 variantsExt.variant('fooVariant') {
308 role('production') { layers('bar') }
309 }
310
311 variantSources {
312 namingPolicy {
313 resolveNameCollision()
314 }
315 }
316
317 afterEvaluate {
318 variantSources.whenFinalized { ctx ->
319 def foo = ctx.variants.variants.find { it.name == 'foo' }
320 def fooVariant = ctx.variants.variants.find { it.name == 'fooVariant' }
321 def variantBar = ctx.variants.layers.find { it.name == 'variantBar' }
322 def bar = ctx.variants.layers.find { it.name == 'bar' }
323
324 def later = ctx.compileUnits.getUnit(fooVariant, bar)
325 def earlier = ctx.compileUnits.getUnit(foo, variantBar)
326
327 println("map1=" + later.variant().name + ":" + later.layer().name + "->" + ctx.sourceSets.getSourceSet(later).name)
328 println("map2=" + earlier.variant().name + ":" + earlier.layer().name + "->" + ctx.sourceSets.getSourceSet(earlier).name)
329 }
330 }
331 """);
332
333 BuildResult result = runner("help").build();
334
335 assertTrue(result.getOutput().contains("map1=fooVariant:bar->fooVariantBar2"));
336 assertTrue(result.getOutput().contains("map2=foo:variantBar->fooVariantBar"));
337 }
338
339 @Test
340 void failsOnUnknownVariantSelectorTarget() throws Exception {
341 writeSettings("variant-sources-missing-variant");
342 writeBuildFile("""
343 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
344
345 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
346 variantsExt.layers.create('main')
347 variantsExt.roles.create('production')
348 variantsExt.variant('browser') {
349 role('production') { layers('main') }
350 }
351
352 variantSources {
353 variant('missing') {
354 declareOutputs('js')
355 }
356 }
357 """);
358
359 assertBuildFails("Variant 'missing' is't declared", "help");
360 }
361
362 @Test
363 void failsOnUnknownLayerSelectorTarget() throws Exception {
364 writeSettings("variant-sources-missing-layer");
365 writeBuildFile("""
366 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
367
368 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
369 variantsExt.layers.create('main')
370 variantsExt.roles.create('production')
371 variantsExt.variant('browser') {
372 role('production') { layers('main') }
373 }
374
375 variantSources {
376 layer('missing') {
377 declareOutputs('js')
378 }
379 }
380 """);
381
382 assertBuildFails("Layer 'missing' isn't declared", "help");
383 }
384
385 @Test
386 void failsOnUndeclaredCompileUnitSelectorTarget() throws Exception {
387 writeSettings("variant-sources-missing-unit");
388 writeBuildFile("""
389 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
390
391 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
392 variantsExt.layers.create('main')
393 variantsExt.layers.create('test')
394 variantsExt.roles.create('production')
395 variantsExt.variant('browser') {
396 role('production') { layers('main') }
397 }
398
399 variantSources {
400 unit('browser', 'test') {
401 declareOutputs('js')
402 }
403 }
404 """);
405
406 assertBuildFails("The CompileUnit isn't declared for variant 'browser', layer 'test'", "help");
407 }
408
409 @Test
410 void rejectsChangingNamingPolicyAfterContextBecomesObservable() throws Exception {
411 writeSettings("variant-sources-name-policy-fixed");
412 writeBuildFile("""
413 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
414
415 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
416 variantsExt.layers.create('main')
417 variantsExt.roles.create('production')
418 variantsExt.variant('browser') {
419 role('production') { layers('main') }
420 }
421
422 variantSources.whenFinalized {
423 variantSources.namingPolicy {
424 resolveNameCollision()
425 }
426 }
427 """);
428
429 assertBuildFails("Naming policy already applied", "help");
430 }
431 }
@@ -0,0 +1,136
1 package org.implab.gradle.variants;
2
3 import static org.junit.jupiter.api.Assertions.assertTrue;
4
5 import org.gradle.testkit.runner.BuildResult;
6 import org.junit.jupiter.api.Test;
7
8 class VariantsPluginFunctionalTest extends AbstractFunctionalTest {
9
10 @Test
11 void replaysFinalizedViewAndExposesResolvedEntries() throws Exception {
12 writeSettings("variants-current-contract");
13 writeBuildFile("""
14 apply plugin: org.implab.gradle.variants.VariantsPlugin
15
16 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
17 variantsExt.layers.create('main')
18 variantsExt.layers.create('test')
19 variantsExt.roles.create('production')
20 variantsExt.roles.create('test')
21
22 variantsExt.variant('browser') {
23 role('production') { layers('main') }
24 role('test') { layers('main', 'test') }
25 }
26
27 def events = []
28
29 variantsExt.whenFinalized { view ->
30 events << "early:entries=" + view.entries
31 .collect { "${it.variant().name}:${it.role().name}:${it.layer().name}" }
32 .sort()
33 .join('|')
34 events << "early:layers=" + view.layers.collect { it.name }.sort().join(',')
35 def production = view.roles.find { it.name == 'production' }
36 def mainLayer = view.layers.find { it.name == 'main' }
37 events << "early:productionEntries=" + view.getEntriesForRole(production).size()
38 events << "early:mainEntries=" + view.getEntriesForLayer(mainLayer).size()
39 }
40
41 afterEvaluate {
42 variantsExt.whenFinalized { view ->
43 events << "late:variants=" + view.variants.collect { it.name }.sort().join(',')
44 }
45 }
46
47 tasks.register('probe') {
48 doLast {
49 events.each { println(it) }
50 }
51 }
52 """);
53
54 BuildResult result = runner("probe").build();
55
56 assertTrue(result.getOutput().contains(
57 "early:entries=browser:production:main|browser:test:main|browser:test:test"));
58 assertTrue(result.getOutput().contains("early:layers=main,test"));
59 assertTrue(result.getOutput().contains("early:productionEntries=1"));
60 assertTrue(result.getOutput().contains("early:mainEntries=2"));
61 assertTrue(result.getOutput().contains("late:variants=browser"));
62 }
63
64 @Test
65 void exposesDeclaredButUnboundIdentitiesInFinalizedView() throws Exception {
66 writeSettings("variants-unbound-identities");
67 writeBuildFile("""
68 apply plugin: org.implab.gradle.variants.VariantsPlugin
69
70 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
71 variantsExt.layers.create('main')
72 variantsExt.layers.create('test')
73 variantsExt.roles.create('production')
74 variantsExt.roles.create('test')
75
76 variantsExt.variant('browser') {
77 role('production') { layers('main') }
78 }
79 variantsExt.variant('node')
80
81 tasks.register('probe') {
82 doLast {
83 variantsExt.whenFinalized { view ->
84 def testRole = view.roles.find { it.name == 'test' }
85 def testLayer = view.layers.find { it.name == 'test' }
86
87 println("variants=" + view.variants.collect { it.name }.sort().join(','))
88 println("entriesForTestRole=" + view.getEntriesForRole(testRole).size())
89 println("entriesForTestLayer=" + view.getEntriesForLayer(testLayer).size())
90 }
91 }
92 }
93 """);
94
95 BuildResult result = runner("probe").build();
96
97 assertTrue(result.getOutput().contains("variants=browser,node"));
98 assertTrue(result.getOutput().contains("entriesForTestRole=0"));
99 assertTrue(result.getOutput().contains("entriesForTestLayer=0"));
100 }
101
102 @Test
103 void failsOnUnknownLayerReferenceDuringFinalization() throws Exception {
104 writeSettings("variants-missing-layer");
105 writeBuildFile("""
106 apply plugin: org.implab.gradle.variants.VariantsPlugin
107
108 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
109 variantsExt.layers.create('main')
110 variantsExt.roles.create('production')
111
112 variantsExt.variant('browser') {
113 role('production') { layers('main', 'missingLayer') }
114 }
115 """);
116
117 assertBuildFails("Layer 'missingLayer' isn't declared, referenced in 'browser' variant with 'production' role", "help");
118 }
119
120 @Test
121 void failsOnUnknownRoleReferenceDuringFinalization() throws Exception {
122 writeSettings("variants-missing-role");
123 writeBuildFile("""
124 apply plugin: org.implab.gradle.variants.VariantsPlugin
125
126 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
127 variantsExt.layers.create('main')
128
129 variantsExt.variant('browser') {
130 role('production') { layers('main') }
131 }
132 """);
133
134 assertBuildFails("Role 'production' isn't declared, referenced in 'browser' variant", "help");
135 }
136 }
@@ -0,0 +1,104
1 package org.implab.gradle.variants.sources;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
6
7 import java.lang.reflect.Constructor;
8 import java.util.Set;
9
10 import org.implab.gradle.variants.core.Layer;
11 import org.implab.gradle.variants.core.Role;
12 import org.implab.gradle.variants.core.Variant;
13 import org.implab.gradle.variants.core.VariantsView;
14 import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;
15 import org.junit.jupiter.api.Test;
16
17 class CompileUnitsViewTest {
18 @Test
19 void deduplicatesCompileUnitsAcrossRolesAndExposesParticipatingRoles() {
20 var browser = new TestVariant("browser");
21 var main = new TestLayer("main");
22 var test = new TestLayer("test");
23 var production = new TestRole("production");
24 var qa = new TestRole("test");
25
26 var view = view(
27 Set.of(main, test),
28 Set.of(production, qa),
29 Set.of(browser),
30 Set.of(
31 new VariantRoleLayer(browser, production, main),
32 new VariantRoleLayer(browser, qa, main),
33 new VariantRoleLayer(browser, qa, test)));
34
35 var units = CompileUnitsView.of(view);
36 var browserMain = units.getUnit(browser, main);
37 var browserTest = units.getUnit(browser, test);
38
39 assertEquals(2, units.getUnits().size());
40 assertEquals(Set.of(browserMain, browserTest), units.getUnitsForVariant(browser));
41 assertEquals(Set.of(production, qa), units.getRoles(browserMain));
42 assertEquals(Set.of(qa), units.getRoles(browserTest));
43 assertTrue(units.contains(browser, main));
44 assertTrue(units.contains(browser, test));
45 }
46
47 @Test
48 void rejectsMissingCompileUnitLookup() {
49 var browser = new TestVariant("browser");
50 var main = new TestLayer("main");
51 var missing = new TestLayer("missing");
52 var production = new TestRole("production");
53
54 var view = view(
55 Set.of(main),
56 Set.of(production),
57 Set.of(browser),
58 Set.of(new VariantRoleLayer(browser, production, main)));
59
60 var units = CompileUnitsView.of(view);
61
62 var ex = assertThrows(IllegalArgumentException.class, () -> units.getUnit(browser, missing));
63 assertTrue(ex.getMessage().contains("Compile unit for variant 'browser' and layer 'missing' not found"));
64 }
65
66 private static VariantsView view(
67 Set<Layer> layers,
68 Set<Role> roles,
69 Set<Variant> variants,
70 Set<VariantRoleLayer> entries) {
71 try {
72 Constructor<VariantsView> ctor = VariantsView.class.getDeclaredConstructor(
73 Set.class,
74 Set.class,
75 Set.class,
76 Set.class);
77 ctor.setAccessible(true);
78 return ctor.newInstance(layers, roles, variants, entries);
79 } catch (Exception e) {
80 throw new RuntimeException("Unable to create VariantsView fixture", e);
81 }
82 }
83
84 private record TestVariant(String value) implements Variant {
85 @Override
86 public String getName() {
87 return value;
88 }
89 }
90
91 private record TestLayer(String value) implements Layer {
92 @Override
93 public String getName() {
94 return value;
95 }
96 }
97
98 private record TestRole(String value) implements Role {
99 @Override
100 public String getName() {
101 return value;
102 }
103 }
104 }
@@ -0,0 +1,105
1 package org.implab.gradle.variants.sources;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
6
7 import java.lang.reflect.Constructor;
8 import java.util.Set;
9
10 import org.implab.gradle.variants.core.Layer;
11 import org.implab.gradle.variants.core.Role;
12 import org.implab.gradle.variants.core.Variant;
13 import org.implab.gradle.variants.core.VariantsView;
14 import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;
15 import org.junit.jupiter.api.Test;
16
17 class RoleProjectionsViewTest {
18 @Test
19 void exposesRoleProjectionsAndTheirCompileUnits() {
20 var browser = new TestVariant("browser");
21 var main = new TestLayer("main");
22 var test = new TestLayer("test");
23 var production = new TestRole("production");
24 var qa = new TestRole("test");
25
26 var view = view(
27 Set.of(main, test),
28 Set.of(production, qa),
29 Set.of(browser),
30 Set.of(
31 new VariantRoleLayer(browser, production, main),
32 new VariantRoleLayer(browser, qa, main),
33 new VariantRoleLayer(browser, qa, test)));
34
35 var projections = RoleProjectionsView.of(view);
36 var productionProjection = projections.getProjection(browser, production);
37 var qaProjection = projections.getProjection(browser, qa);
38
39 assertEquals(Set.of(productionProjection, qaProjection), projections.getProjections());
40 assertEquals(Set.of(productionProjection, qaProjection), projections.getProjectionsForVariant(browser));
41 assertEquals(Set.of(qaProjection), projections.getProjectionsForRole(qa));
42 assertEquals(Set.of(new CompileUnit(browser, main)), projections.getUnits(productionProjection));
43 assertEquals(Set.of(new CompileUnit(browser, main), new CompileUnit(browser, test)), projections.getUnits(qaProjection));
44 assertEquals(Set.of(main, test), projections.getLayers(qaProjection));
45 assertTrue(projections.contains(browser, production));
46 }
47
48 @Test
49 void rejectsMissingProjectionLookup() {
50 var browser = new TestVariant("browser");
51 var node = new TestVariant("node");
52 var main = new TestLayer("main");
53 var production = new TestRole("production");
54
55 var view = view(
56 Set.of(main),
57 Set.of(production),
58 Set.of(browser, node),
59 Set.of(new VariantRoleLayer(browser, production, main)));
60
61 var projections = RoleProjectionsView.of(view);
62
63 var ex = assertThrows(IllegalArgumentException.class, () -> projections.getProjection(node, production));
64 assertTrue(ex.getMessage().contains("Role projection for variant 'node' and role 'production' not found"));
65 }
66
67 private static VariantsView view(
68 Set<Layer> layers,
69 Set<Role> roles,
70 Set<Variant> variants,
71 Set<VariantRoleLayer> entries) {
72 try {
73 Constructor<VariantsView> ctor = VariantsView.class.getDeclaredConstructor(
74 Set.class,
75 Set.class,
76 Set.class,
77 Set.class);
78 ctor.setAccessible(true);
79 return ctor.newInstance(layers, roles, variants, entries);
80 } catch (Exception e) {
81 throw new RuntimeException("Unable to create VariantsView fixture", e);
82 }
83 }
84
85 private record TestVariant(String value) implements Variant {
86 @Override
87 public String getName() {
88 return value;
89 }
90 }
91
92 private record TestLayer(String value) implements Layer {
93 @Override
94 public String getName() {
95 return value;
96 }
97 }
98
99 private record TestRole(String value) implements Role {
100 @Override
101 public String getName() {
102 return value;
103 }
104 }
105 }
@@ -0,0 +1,90
1 package org.implab.gradle.variants.sources.internal;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
6
7 import java.util.List;
8
9 import org.gradle.api.InvalidUserDataException;
10 import org.implab.gradle.variants.core.Layer;
11 import org.implab.gradle.variants.core.Variant;
12 import org.implab.gradle.variants.sources.CompileUnit;
13 import org.junit.jupiter.api.Test;
14
15 class CompileUnitNamerTest {
16 @Test
17 void resolvesProjectedNameForUniqueCompileUnit() {
18 var unit = unit("browser", "main");
19
20 var namer = CompileUnitNamer.builder()
21 .addUnits(List.of(unit))
22 .build();
23
24 assertEquals("browserMain", namer.resolveName(unit));
25 }
26
27 @Test
28 void failsOnProjectedNameCollisionInFailMode() {
29 var left = unit("foo", "variantBar");
30 var right = unit("fooVariant", "bar");
31
32 var ex = assertThrows(
33 InvalidUserDataException.class,
34 () -> CompileUnitNamer.builder()
35 .addUnits(List.of(left, right))
36 .nameCollisionPolicy(NameCollisionPolicy.FAIL)
37 .build());
38
39 assertTrue(ex.getMessage().contains("The same source set names are produced by different compile units"));
40 assertTrue(ex.getMessage().contains("fooVariantBar"));
41 }
42
43 @Test
44 void resolvesProjectedNameCollisionDeterministicallyInCanonicalOrder() {
45 var earlier = unit("foo", "variantBar");
46 var later = unit("fooVariant", "bar");
47
48 var namer = CompileUnitNamer.builder()
49 .addUnits(List.of(later, earlier))
50 .nameCollisionPolicy(NameCollisionPolicy.RESOLVE)
51 .build();
52
53 assertEquals("fooVariantBar", namer.resolveName(earlier));
54 assertEquals("fooVariantBar2", namer.resolveName(later));
55 }
56
57 @Test
58 void rejectsUnknownCompileUnitLookup() {
59 var known = unit("browser", "main");
60 var unknown = unit("browser", "test");
61
62 var namer = CompileUnitNamer.builder()
63 .addUnits(List.of(known))
64 .build();
65
66 var ex = assertThrows(IllegalArgumentException.class, () -> namer.resolveName(unknown));
67 assertTrue(ex.getMessage().contains("Compile unit"));
68 assertTrue(ex.getMessage().contains("associated name"));
69 }
70
71 private static CompileUnit unit(String variantName, String layerName) {
72 return new CompileUnit(
73 new TestVariant(variantName),
74 new TestLayer(layerName));
75 }
76
77 private record TestVariant(String value) implements Variant {
78 @Override
79 public String getName() {
80 return value;
81 }
82 }
83
84 private record TestLayer(String value) implements Layer {
85 @Override
86 public String getName() {
87 return value;
88 }
89 }
90 }
@@ -0,0 +1,42
1 package org.implab.gradle.variants.sources.internal;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
5
6 import org.junit.jupiter.api.Test;
7
8 class DefaultCompileUnitNamingPolicyTest {
9 @Test
10 void usesFailPolicyByDefault() {
11 var policy = new DefaultCompileUnitNamingPolicy();
12
13 assertEquals(NameCollisionPolicy.FAIL, policy.policy());
14 }
15
16 @Test
17 void switchesToResolvePolicyWhenSelected() {
18 var policy = new DefaultCompileUnitNamingPolicy();
19
20 policy.resolveNameCollision();
21
22 assertEquals(NameCollisionPolicy.RESOLVE, policy.policy());
23 }
24
25 @Test
26 void rejectsChangingPolicyAfterItWasSelected() {
27 var policy = new DefaultCompileUnitNamingPolicy();
28
29 policy.resolveNameCollision();
30
31 assertThrows(IllegalStateException.class, policy::failOnNameCollision);
32 }
33
34 @Test
35 void rejectsChangingPolicyAfterItWasFinalized() {
36 var policy = new DefaultCompileUnitNamingPolicy();
37
38 policy.finalizePolicy();
39
40 assertThrows(IllegalStateException.class, policy::resolveNameCollision);
41 }
42 }
@@ -0,0 +1,51
1 package org.implab.gradle.variants.sources.internal;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
5
6 import org.junit.jupiter.api.Test;
7
8 class DefaultLateConfigurationPolicySpecTest {
9 @Test
10 void usesFailModeByDefault() {
11 var policy = new DefaultLateConfigurationPolicySpec();
12
13 assertEquals(LateConfigurationMode.FAIL, policy.mode());
14 }
15
16 @Test
17 void switchesToWarnModeWhenSelected() {
18 var policy = new DefaultLateConfigurationPolicySpec();
19
20 policy.warnOnLateConfiguration();
21
22 assertEquals(LateConfigurationMode.WARN, policy.mode());
23 }
24
25 @Test
26 void switchesToApplyModeWhenSelected() {
27 var policy = new DefaultLateConfigurationPolicySpec();
28
29 policy.allowLateConfiguration();
30
31 assertEquals(LateConfigurationMode.APPLY, policy.mode());
32 }
33
34 @Test
35 void rejectsChangingPolicyAfterItWasSelected() {
36 var policy = new DefaultLateConfigurationPolicySpec();
37
38 policy.warnOnLateConfiguration();
39
40 assertThrows(IllegalStateException.class, policy::failOnLateConfiguration);
41 }
42
43 @Test
44 void rejectsChangingPolicyAfterItWasFinalized() {
45 var policy = new DefaultLateConfigurationPolicySpec();
46
47 policy.finalizePolicy();
48
49 assertThrows(IllegalStateException.class, policy::allowLateConfiguration);
50 }
51 }
@@ -1,164 +1,168
1 package org.implab.gradle.variants.sources;
1 package org.implab.gradle.variants.sources;
2
2
3 import org.eclipse.jdt.annotation.NonNullByDefault;
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 import org.gradle.api.Action;
4 import org.gradle.api.Action;
5 import org.implab.gradle.common.core.lang.Closures;
5 import org.implab.gradle.common.core.lang.Closures;
6 import org.implab.gradle.common.sources.GenericSourceSet;
6 import org.implab.gradle.common.sources.GenericSourceSet;
7 import groovy.lang.Closure;
7 import groovy.lang.Closure;
8
8
9 @NonNullByDefault
9 @NonNullByDefault
10 public interface VariantSourcesExtension {
10 public interface VariantSourcesExtension {
11
11
12 /**
12 /**
13 * Selects how selector rules behave when they target an already materialized
13 * Selects how selector rules behave when they target an already materialized
14 * {@link GenericSourceSet}.
14 * {@link GenericSourceSet}.
15 *
15 *
16 * <p>This policy is single-valued:
16 * <p>This policy is single-valued:
17 * <ul>
17 * <ul>
18 * <li>it must be selected before the first selector rule is registered via
18 * <li>it must be selected before the first selector rule is registered via
19 * {@link #variant(String, Action)}, {@link #layer(String, Action)} or
19 * {@link #variant(String, Action)}, {@link #layer(String, Action)} or
20 * {@link #unit(String, String, Action)};</li>
20 * {@link #unit(String, String, Action)};</li>
21 * <li>once selected, it cannot be changed later;</li>
21 * <li>once selected, it cannot be changed later;</li>
22 * <li>the policy controls both diagnostics and late-application semantics.</li>
22 * <li>the policy controls both diagnostics and late-application semantics.</li>
23 * </ul>
23 * </ul>
24 *
24 *
25 * <p>If not selected explicitly, the default is
25 * <p>If not selected explicitly, the default is
26 * {@link LateConfigurationPolicySpec#failOnLateConfiguration()}.
26 * {@link LateConfigurationPolicySpec#failOnLateConfiguration()}.
27 */
27 */
28 void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action);
28 void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action);
29
29
30 default void lateConfigurationPolicy(Closure<?> closure) {
30 default void lateConfigurationPolicy(Closure<?> closure) {
31 lateConfigurationPolicy(Closures.action(closure));
31 lateConfigurationPolicy(Closures.action(closure));
32 }
32 }
33
33
34 /**
34 /**
35 * Selects how compile-unit name collisions are handled when the finalized
35 * Selects how compile-unit name collisions are handled when the finalized
36 * source context is created.
36 * source context is created.
37 *
37 *
38 * <p>This policy is single-valued:
38 * <p>This policy is single-valued:
39 * <ul>
39 * <ul>
40 * <li>it must be selected before the finalized
40 * <li>it must be selected before the finalized
41 * {@link VariantSourcesContext} becomes observable through
41 * {@link VariantSourcesContext} becomes observable through
42 * {@link #whenFinalized(Action)};</li>
42 * {@link #whenFinalized(Action)};</li>
43 * <li>once the context is being created, the policy is fixed and cannot be
43 * <li>once the context is being created, the policy is fixed and cannot be
44 * changed later;</li>
44 * changed later;</li>
45 * <li>the policy governs validation of compile-unit names produced by the
45 * <li>the policy governs validation of compile-unit names produced by the
46 * source-set materializer.</li>
46 * source-set materializer.</li>
47 * </ul>
47 * </ul>
48 *
48 *
49 * <p>If not selected explicitly, the default is
49 * <p>If not selected explicitly, the default is
50 * {@link NamingPolicySpec#failOnNameCollision()}.
50 * {@link NamingPolicySpec#failOnNameCollision()}.
51 */
51 */
52 void namingPolicy(Action<? super NamingPolicySpec> action);
52 void namingPolicy(Action<? super NamingPolicySpec> action);
53
53
54 default void namingPolicy(Closure<?> closure) {
54 default void namingPolicy(Closure<?> closure) {
55 namingPolicy(Closures.action(closure));
55 namingPolicy(Closures.action(closure));
56 }
56 }
57
57
58 /**
58 /**
59 * Registers a selector rule for all compile units of the given layer.
59 * Registers a selector rule for all compile units of the given layer.
60 *
60 *
61 * <p>Registering the first selector rule fixes the selected
61 * <p>Registering the first selector rule fixes the selected
62 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
62 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
63 * lifecycle.
63 * lifecycle.
64 */
64 */
65 void layer(String layerName, Action<? super GenericSourceSet> action);
65 void layer(String layerName, Action<? super GenericSourceSet> action);
66
66
67 default void layer(String layerName, Closure<?> closure) {
67 default void layer(String layerName, Closure<?> closure) {
68 layer(layerName, Closures.action(closure));
68 layer(layerName, Closures.action(closure));
69 }
69 }
70
70
71 /**
71 /**
72 * Registers a selector rule for all compile units of the given variant.
72 * Registers a selector rule for all compile units of the given variant.
73 *
73 *
74 * <p>Registering the first selector rule fixes the selected
74 * <p>Registering the first selector rule fixes the selected
75 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
75 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
76 * lifecycle.
76 * lifecycle.
77 */
77 */
78 void variant(String variantName, Action<? super GenericSourceSet> action);
78 void variant(String variantName, Action<? super GenericSourceSet> action);
79
79
80 default void variant(String variantName, Closure<?> closure) {
80 default void variant(String variantName, Closure<?> closure) {
81 variant(variantName, Closures.action(closure));
81 variant(variantName, Closures.action(closure));
82 }
82 }
83
83
84 /**
84 /**
85 * Registers a selector rule for one exact compile unit.
85 * Registers a selector rule for one exact compile unit.
86 *
86 *
87 * <p>Registering the first selector rule fixes the selected
87 * <p>Registering the first selector rule fixes the selected
88 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
88 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
89 * lifecycle.
89 * lifecycle.
90 */
90 */
91 void unit(String variantName, String layerName, Action<? super GenericSourceSet> action);
91 void unit(String variantName, String layerName, Action<? super GenericSourceSet> action);
92
92
93 default void unit(String variantName, String layerName, Closure<?> closure) {
94 unit(variantName, layerName, Closures.action(closure));
95 }
96
93 /**
97 /**
94 * Invoked when finalized variants-derived source context becomes available.
98 * Invoked when finalized variants-derived source context becomes available.
95 *
99 *
96 * Replayable:
100 * Replayable:
97 * <ul>
101 * <ul>
98 * <li>if called before variants finalization, action is queued
102 * <li>if called before variants finalization, action is queued
99 * <li>if called after variants finalization, action is invoked immediately
103 * <li>if called after variants finalization, action is invoked immediately
100 * </ul>
104 * </ul>
101 *
105 *
102 * <p>By the time this callback becomes observable, compile-unit naming
106 * <p>By the time this callback becomes observable, compile-unit naming
103 * policy has already been fixed and symbolic source-set names for finalized
107 * policy has already been fixed and symbolic source-set names for finalized
104 * compile units are determined.
108 * compile units are determined.
105 */
109 */
106 void whenFinalized(Action<? super VariantSourcesContext> action);
110 void whenFinalized(Action<? super VariantSourcesContext> action);
107
111
108 default void whenFinalized(Closure<?> closure) {
112 default void whenFinalized(Closure<?> closure) {
109 whenFinalized(Closures.action(closure));
113 whenFinalized(Closures.action(closure));
110 }
114 }
111
115
112
116
113 /**
117 /**
114 * Imperative selector for the late-configuration mode.
118 * Imperative selector for the late-configuration mode.
115 *
119 *
116 * <p>Exactly one mode is expected to be chosen for the extension lifecycle.
120 * <p>Exactly one mode is expected to be chosen for the extension lifecycle.
117 */
121 */
118 interface LateConfigurationPolicySpec {
122 interface LateConfigurationPolicySpec {
119 /**
123 /**
120 * Rejects selector registration if it targets any already materialized
124 * Rejects selector registration if it targets any already materialized
121 * source set.
125 * source set.
122 */
126 */
123 void failOnLateConfiguration();
127 void failOnLateConfiguration();
124
128
125 /**
129 /**
126 * Allows late selector registration, but emits a warning when it targets an
130 * Allows late selector registration, but emits a warning when it targets an
127 * already materialized source set.
131 * already materialized source set.
128 *
132 *
129 * <p>For such targets, selector precedence is not re-established
133 * <p>For such targets, selector precedence is not re-established
130 * retroactively. The action is applied as a late imperative step, after the
134 * retroactively. The action is applied as a late imperative step, after the
131 * state already produced at the materialization moment.
135 * state already produced at the materialization moment.
132 */
136 */
133 void warnOnLateConfiguration();
137 void warnOnLateConfiguration();
134
138
135 /**
139 /**
136 * Allows late selector registration without a warning when it targets an
140 * Allows late selector registration without a warning when it targets an
137 * already materialized source set.
141 * already materialized source set.
138 *
142 *
139 * <p>For such targets, selector precedence is not re-established
143 * <p>For such targets, selector precedence is not re-established
140 * retroactively. The action is applied as a late imperative step, after the
144 * retroactively. The action is applied as a late imperative step, after the
141 * state already produced at the materialization moment.
145 * state already produced at the materialization moment.
142 */
146 */
143 void allowLateConfiguration();
147 void allowLateConfiguration();
144 }
148 }
145
149
146 interface NamingPolicySpec {
150 interface NamingPolicySpec {
147 /**
151 /**
148 * Rejects finalized compile-unit models that project the same source-set
152 * Rejects finalized compile-unit models that project the same source-set
149 * name for different compile units.
153 * name for different compile units.
150 */
154 */
151 void failOnNameCollision();
155 void failOnNameCollision();
152
156
153 /**
157 /**
154 * Resolves name collisions deterministically for the finalized
158 * Resolves name collisions deterministically for the finalized
155 * compile-unit model.
159 * compile-unit model.
156 *
160 *
157 * <p>Conflicting compile units are ordered canonically by
161 * <p>Conflicting compile units are ordered canonically by
158 * {@code (variant.name, layer.name)}. The first unit keeps the base
162 * {@code (variant.name, layer.name)}. The first unit keeps the base
159 * projected name, and each next unit receives a numeric suffix
163 * projected name, and each next unit receives a numeric suffix
160 * ({@code 2}, {@code 3}, ...).
164 * ({@code 2}, {@code 3}, ...).
161 */
165 */
162 void resolveNameCollision();
166 void resolveNameCollision();
163 }
167 }
164 }
168 }
General Comments 0
You need to be logged in to leave comments. Login now