Skip to content

Commit 3d02455

Browse files
author
Markus Paulsen
committed
Anchor the reference scan, and repair a sentence that lost its verb
Four findings on what the previous two commits added. "Which mechanism does the intercepting comes from the policy's" is not a sentence. It reads "The interception mechanism comes from" now. The scan used two roots for one repository: it walked from the resolved repository root and then resolved each reference, and each file it read, against the working directory of the JVM. Those are the same under Surefire today, which is why it passed. The day they are not, every reference reports as broken and the message points at the documentation for what would be a change in configuration. Everything now resolves against the one root, and the SITE constant that was declared and never read builds the pattern that looks for it. The scan also walked the whole tree three times, once per test, filtering the skipped directories afterwards rather than pruning them. The site's node_modules holds tens of thousands of entries, and descending into it to discard everything found there was most of what this test did: 5.2 seconds before, 0.4 after. It is collected once now, with the generated directories skipped as subtrees, and an entry that cannot be read is walked past rather than ending the traversal, which also closes the UncheckedIOException that the surrounding catch did not cover. The fixture assertion reported "is not scanned" for two different causes. A renamed file now says the list is stale, and a file that exists but is missed says the scan has a hole, because those call for opposite responses. One suggestion not taken: "arms the runtime interception the policy asks for" is a reduced relative clause, not an error, and it reads better here than the passive alternative.
1 parent 949175d commit 3d02455

2 files changed

Lines changed: 80 additions & 24 deletions

File tree

documentation/docs/instructor/ares-2/what-does-ares-2-protect-against.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ This mode activates dynamically when tests run, and gives immediate, fine-graine
6868
student code execution without requiring pre-generated test files.
6969

7070
:::warning[The mode does not decide the interception mechanism]
71-
Which mechanism does the intercepting comes from the policy's
71+
The interception mechanism comes from the policy's
7272
`theFollowingProgrammingLanguageConfigurationIsUsed`, not from the mode. An `_ASPECTJ`
7373
configuration needs the aspects woven into the bytecode by the AspectJ compiler during the
7474
build; an `_INSTRUMENTATION` configuration needs the Byte Buddy agent attached to the test JVM.

src/test/java/de/tum/cit/ase/ares/documentation/DocumentationReferenceTest.java

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@
77
import java.io.UncheckedIOException;
88
import java.nio.charset.MalformedInputException;
99
import java.nio.charset.StandardCharsets;
10+
import java.nio.file.FileVisitResult;
1011
import java.nio.file.Files;
1112
import java.nio.file.Path;
13+
import java.nio.file.SimpleFileVisitor;
14+
import java.nio.file.attribute.BasicFileAttributes;
1215
import java.util.ArrayList;
1316
import java.util.List;
17+
import java.util.Locale;
1418
import java.util.Set;
1519
import java.util.regex.Matcher;
1620
import java.util.regex.Pattern;
17-
import java.util.stream.Stream;
1821

22+
import org.junit.jupiter.api.BeforeAll;
1923
import org.junit.jupiter.api.DisplayName;
2024
import org.junit.jupiter.api.Test;
2125

@@ -43,7 +47,16 @@
4347
@DisplayName("References into the documentation")
4448
class DocumentationReferenceTest {
4549

46-
private static final Path REPOSITORY = Path.of("");
50+
/**
51+
* The repository root, resolved once.
52+
* <p>
53+
* Every path here is resolved against it rather than against the working
54+
* directory of the JVM. The two are the same under Surefire today, and a check
55+
* that quietly depends on that would report every reference as broken the day
56+
* they diverge, pointing at the documentation for what would be a configuration
57+
* change.
58+
*/
59+
private static final Path REPOSITORY = Path.of("").toAbsolutePath().normalize();
4760

4861
/** Where the documentation lives now. */
4962
private static final String SITE = "documentation/docs/";
@@ -58,7 +71,7 @@ class DocumentationReferenceTest {
5871
private static final Pattern REMOVED_TREE = Pattern
5972
.compile("(?<!documentation/)(?<![\\w.-])docs/[A-Za-z0-9_./*-]+");
6073

61-
private static final Pattern REFERENCE = Pattern.compile("documentation/docs/[A-Za-z0-9_./*-]+");
74+
private static final Pattern REFERENCE = Pattern.compile(Pattern.quote(SITE) + "[A-Za-z0-9_./*-]+");
6275

6376
/**
6477
* Directories holding no reference worth resolving: the site itself, whose
@@ -122,9 +135,17 @@ void theScanReachesTheFilesThatCarriedTheBrokenReferences() {
122135

123136
for (String file : List.of("examples/README.md", "examples/ares-exercise-maven/pom.xml",
124137
"examples/ares-exercise-gradle/build.gradle", "tools/pointcut_comparison.R", ".coderabbit.yaml")) {
138+
// Existence first, because the two causes call for opposite responses: a
139+
// renamed file means this list is stale, while a file that is there and not
140+
// scanned means the scan has a hole. One message for both would send a
141+
// reader after the wrong one.
142+
assertTrue(Files.exists(REPOSITORY.resolve(file)),
143+
file + " no longer exists, so this list is out of date rather than the scan. Name the file "
144+
+ "that replaced it, or drop the entry if nothing points into the documentation from "
145+
+ "there any more.");
125146
assertTrue(scanned.contains(file),
126-
file + " is not scanned, so a reference in it would go unnoticed. It held one of the "
127-
+ "references this test exists for.");
147+
file + " exists but is not scanned, so a reference in it would go unnoticed. It held one of "
148+
+ "the references this test exists for.");
128149
}
129150
}
130151

@@ -136,11 +157,11 @@ void theScanReachesTheFilesThatCarriedTheBrokenReferences() {
136157
private static boolean resolves(String reference) {
137158
int glob = reference.indexOf('*');
138159
if (glob < 0) {
139-
return Files.exists(Path.of(reference));
160+
return Files.exists(REPOSITORY.resolve(reference));
140161
}
141162
String prefix = reference.substring(0, glob);
142163
int lastSeparator = prefix.lastIndexOf('/');
143-
return lastSeparator > 0 && Files.isDirectory(Path.of(prefix.substring(0, lastSeparator)));
164+
return lastSeparator > 0 && Files.isDirectory(REPOSITORY.resolve(prefix.substring(0, lastSeparator)));
144165
}
145166

146167
/**
@@ -155,23 +176,58 @@ private static String trimProse(String reference) {
155176
return trimmed;
156177
}
157178

158-
private static List<Path> scannedFiles() {
159-
try (Stream<Path> entries = Files.walk(REPOSITORY.toAbsolutePath().normalize())) {
160-
Path root = REPOSITORY.toAbsolutePath().normalize();
161-
return entries.filter(Files::isRegularFile).map(root::relativize)
162-
.filter(DocumentationReferenceTest::isScanned).sorted().toList();
179+
/**
180+
* The files to scan, collected once for the whole class.
181+
* <p>
182+
* Once rather than per test, and pruned rather than filtered afterwards. The
183+
* skipped directories are not merely uninteresting: the site's
184+
* {@code node_modules} holds tens of thousands of entries, and descending into
185+
* it three times to discard everything found there is most of what this test
186+
* would spend its time on.
187+
*/
188+
private static List<Path> scannedFiles;
189+
190+
@BeforeAll
191+
static void collectTheFilesToScan() {
192+
List<Path> found = new ArrayList<>();
193+
try {
194+
Files.walkFileTree(REPOSITORY, new SimpleFileVisitor<>() {
195+
@Override
196+
public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attributes) {
197+
return SKIPPED_DIRECTORIES.contains(directory.getFileName().toString())
198+
? FileVisitResult.SKIP_SUBTREE
199+
: FileVisitResult.CONTINUE;
200+
}
201+
202+
@Override
203+
public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) {
204+
Path relative = REPOSITORY.relativize(file);
205+
if (hasScannedSuffix(relative)) {
206+
found.add(relative);
207+
}
208+
return FileVisitResult.CONTINUE;
209+
}
210+
211+
@Override
212+
public FileVisitResult visitFileFailed(Path file, IOException failure) {
213+
// An entry that cannot be read carries no reference this test can check,
214+
// and it is not what the test is about. Walking on beats failing here.
215+
return FileVisitResult.CONTINUE;
216+
}
217+
});
163218
} catch (IOException exception) {
164-
throw new UncheckedIOException("Could not walk the repository", exception);
219+
throw new UncheckedIOException("Could not walk " + REPOSITORY, exception);
165220
}
221+
found.sort(Path::compareTo);
222+
scannedFiles = List.copyOf(found);
166223
}
167224

168-
private static boolean isScanned(Path relative) {
169-
for (Path segment : relative) {
170-
if (SKIPPED_DIRECTORIES.contains(segment.toString())) {
171-
return false;
172-
}
173-
}
174-
String name = relative.getFileName().toString().toLowerCase(java.util.Locale.ROOT);
225+
private static List<Path> scannedFiles() {
226+
return scannedFiles;
227+
}
228+
229+
private static boolean hasScannedSuffix(Path relative) {
230+
String name = relative.getFileName().toString().toLowerCase(Locale.ROOT);
175231
int dot = name.lastIndexOf('.');
176232
return dot >= 0 && SCANNED_SUFFIXES.contains(name.substring(dot));
177233
}
@@ -181,13 +237,13 @@ private static boolean isScanned(Path relative) {
181237
* scan is by suffix, and a suffix is a guess: a .txt fixture may hold bytes
182238
* that are not UTF-8, and that is not a broken reference.
183239
*/
184-
private static String read(Path file) {
240+
private static String read(Path relative) {
185241
try {
186-
return Files.readString(file, StandardCharsets.UTF_8);
242+
return Files.readString(REPOSITORY.resolve(relative), StandardCharsets.UTF_8);
187243
} catch (MalformedInputException notText) {
188244
return "";
189245
} catch (IOException exception) {
190-
throw new UncheckedIOException("Could not read " + file, exception);
246+
throw new UncheckedIOException("Could not read " + relative, exception);
191247
}
192248
}
193249
}

0 commit comments

Comments
 (0)