@@ -8,12 +8,14 @@ import assertk.assertions.isInstanceOf
8
8
import assertk.assertions.isNotEmpty
9
9
import assertk.fail
10
10
import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.SHADOW_JAR_TASK_NAME
11
+ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction.Companion.CONSTANT_TIME_FOR_ZIP_ENTRIES
11
12
import com.github.jengelman.gradle.plugins.shadow.util.Issue
12
13
import com.github.jengelman.gradle.plugins.shadow.util.containsEntries
13
14
import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries
14
15
import java.net.URLClassLoader
15
16
import kotlin.io.path.appendText
16
17
import kotlin.io.path.writeText
18
+ import kotlin.time.Duration.Companion.seconds
17
19
import org.junit.jupiter.api.Test
18
20
import org.junit.jupiter.params.ParameterizedTest
19
21
import org.junit.jupiter.params.provider.Arguments
@@ -347,6 +349,89 @@ class RelocationTest : BasePluginTest() {
347
349
assertThat(excluded).isEmpty()
348
350
}
349
351
352
+ @ParameterizedTest
353
+ @MethodSource(" preserveLastModifiedProvider" )
354
+ fun preserveLastModifiedCorrectly (enableRelocation : Boolean , preserveFileTimestamps : Boolean ) {
355
+ // Minus 3 sec to avoid the time difference between the file system and the JVM.
356
+ val currentTimeMillis = System .currentTimeMillis() - 3 .seconds.inWholeMilliseconds
357
+ val junitEntryTimeRange = junitRawEntries.map { it.time }.let { it.min().. it.max() }
358
+ writeMainClass(withImports = true )
359
+ projectScriptPath.appendText(
360
+ """
361
+ dependencies {
362
+ implementation 'junit:junit:3.8.2'
363
+ }
364
+ $shadowJar {
365
+ enableRelocation = $enableRelocation
366
+ preserveFileTimestamps = $preserveFileTimestamps
367
+ }
368
+ """ .trimIndent(),
369
+ )
370
+
371
+ run (shadowJarTask)
372
+
373
+ if (enableRelocation) {
374
+ val (relocatedEntries, otherEntries) = outputShadowJar.use {
375
+ it.entries().toList().partition { entry -> entry.name.startsWith(" shadow/" ) }
376
+ }
377
+ assertThat(relocatedEntries).isNotEmpty()
378
+ assertThat(otherEntries).isNotEmpty()
379
+ val (relocatedDirs, relocatedClasses) = relocatedEntries.partition { it.isDirectory }
380
+ assertThat(relocatedDirs).isNotEmpty()
381
+ assertThat(relocatedClasses).isNotEmpty()
382
+
383
+ if (preserveFileTimestamps) {
384
+ relocatedClasses.forEach { entry ->
385
+ // Relocated files should preserve the last modified time of the original files.
386
+ if (entry.time !in junitEntryTimeRange) {
387
+ fail(" Relocated file ${entry.name} has an invalid last modified time: ${entry.time} " )
388
+ }
389
+ }
390
+ (relocatedDirs + otherEntries).forEach { entry ->
391
+ // Relocated directories and other entries are newly created, so they should be in now time.
392
+ if (entry.time < currentTimeMillis) {
393
+ fail(" Relocated directory ${entry.name} has an invalid last modified time: ${entry.time} " )
394
+ }
395
+ }
396
+ } else {
397
+ (relocatedEntries + otherEntries).forEach { entry ->
398
+ // All entries should be newly modified, that default to CONSTANT_TIME_FOR_ZIP_ENTRIES.
399
+ if (entry.time != CONSTANT_TIME_FOR_ZIP_ENTRIES ) {
400
+ fail(" Entry ${entry.name} has an invalid last modified time: ${entry.time} " )
401
+ }
402
+ }
403
+ }
404
+ } else {
405
+ val (shadowedEntries, otherEntries) = outputShadowJar.use {
406
+ it.entries().toList().partition { entry -> entry.name.startsWith(" junit/" ) }
407
+ }
408
+ assertThat(shadowedEntries).isNotEmpty()
409
+ assertThat(otherEntries).isNotEmpty()
410
+
411
+ if (preserveFileTimestamps) {
412
+ shadowedEntries.forEach { entry ->
413
+ // Shadowed entries should preserve the last modified time of the original entries.
414
+ if (entry.time !in junitEntryTimeRange) {
415
+ fail(" Shadowed entry ${entry.name} has an invalid last modified time: ${entry.time} " )
416
+ }
417
+ }
418
+ otherEntries.forEach { entry ->
419
+ // Other entries are newly created, so they should be in now time.
420
+ if (entry.time < currentTimeMillis) {
421
+ fail(" Entry ${entry.name} has an invalid last modified time: ${entry.time} " )
422
+ }
423
+ }
424
+ } else {
425
+ (shadowedEntries + otherEntries).forEach { entry ->
426
+ // All entries should be newly modified, defaults to CONSTANT_TIME_FOR_ZIP_ENTRIES.
427
+ if (entry.time != CONSTANT_TIME_FOR_ZIP_ENTRIES ) {
428
+ fail(" Entry ${entry.name} has an invalid last modified time: ${entry.time} " )
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
434
+
350
435
private companion object {
351
436
@JvmStatic
352
437
fun prefixProvider () = listOf (
@@ -355,5 +440,13 @@ class RelocationTest : BasePluginTest() {
355
440
Arguments .of(" new.pkg" ),
356
441
Arguments .of(" new/path" ),
357
442
)
443
+
444
+ @JvmStatic
445
+ fun preserveLastModifiedProvider () = listOf (
446
+ Arguments .of(false , false ),
447
+ Arguments .of(true , false ),
448
+ Arguments .of(false , true ),
449
+ Arguments .of(true , true ),
450
+ )
358
451
}
359
452
}
0 commit comments