Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.approvej.approve;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;

Expand All @@ -13,6 +14,8 @@
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import org.approvej.configuration.Configuration;
import org.approvej.review.FileReviewer;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

Expand Down Expand Up @@ -200,13 +203,97 @@ static void reset() {
reset(DEFAULT_INVENTORY_FILE);
}

/**
* Finds all received files corresponding to inventory entries.
*
* <p>For each approved file in the inventory, checks if a corresponding received file exists next
* to it.
*
* @return a sorted list of paths to received files that exist
*/
static List<Path> findReceivedFiles() {
return loadInventory().keySet().stream()
.map(ApprovedFileInventory::receivedPathFor)
.filter(Files::exists)
.sorted()
.toList();
}

private static Path receivedPathFor(Path approvedPath) {
String filename = approvedPath.getFileName().toString();
int approvedWithExtIdx = filename.lastIndexOf("-approved.");
if (approvedWithExtIdx >= 0) {
return approvedPath
.getParent()
.resolve(
filename.substring(0, approvedWithExtIdx)
+ "-received."
+ filename.substring(approvedWithExtIdx + "-approved.".length()));
}
if (filename.endsWith("-approved")) {
return approvedPath
.getParent()
.resolve(filename.substring(0, filename.length() - "-approved".length()) + "-received");
}
return approvedPath;
}

/**
* Approves all unapproved files by moving each received file to its corresponding approved file.
*
* @return the list of received file paths that were approved
*/
static List<Path> approveAll() {
List<Path> approved = new ArrayList<>();
for (Path approvedPath : loadInventory().keySet()) {
Path received = receivedPathFor(approvedPath);
if (!Files.exists(received)) {
continue;
}
try {
Files.move(received, approvedPath, REPLACE_EXISTING);
approved.add(received);
} catch (IOException e) {
System.err.printf("Failed to approve %s: %s%n", received, e.getMessage());
}
}
return approved;
}

/**
* Reviews all unapproved files using the configured {@link FileReviewer}.
*
* @param reviewer the {@link FileReviewer} to use for reviewing each unapproved file
*/
static void reviewUnapproved(FileReviewer reviewer) {
var unapprovedEntries =
loadInventory().keySet().stream()
.filter(approvedPath -> Files.exists(receivedPathFor(approvedPath)))
.sorted()
.toList();
if (unapprovedEntries.isEmpty()) {
System.out.println("No unapproved files found.");
return;
}
System.out.println("Unapproved files:");
unapprovedEntries.forEach(
approvedPath -> {
Path receivedPath = receivedPathFor(approvedPath);
System.out.printf(" %s%n", receivedPath.toUri());
reviewer.apply(PathProviders.approvedPath(approvedPath));
});
}

/**
* CLI entry point for build tool plugins.
*
* @param args {@code --find} to list leftovers, {@code --remove} to delete them
* @param args {@code --find} to list leftovers, {@code --remove} to delete them, {@code
* --approve-all} to approve all unapproved files, {@code --review-unapproved} to review all
* unapproved files
*/
public static void main(String[] args) {
String usage = "Usage: ApprovedFileInventory --find | --remove";
String usage =
"Usage: ApprovedFileInventory --find | --remove | --approve-all | --review-unapproved";
if (args.length == 0) {
System.err.println(usage);
System.exit(1);
Expand Down Expand Up @@ -236,6 +323,18 @@ public static void main(String[] args) {
removed.forEach(leftover -> System.out.printf(" %s%n", leftover.relativePath().toUri()));
}
}
case "--approve-all" -> {
List<Path> approved = approveAll();
if (approved.isEmpty()) {
System.out.println("No unapproved files found.");
} else {
System.out.println("Approved files:");
approved.forEach(path -> System.out.printf(" %s%n", path.toUri()));
}
}
case "--review-unapproved" -> {
reviewUnapproved(Configuration.configuration.defaultFileReviewer());
}
default -> {
System.err.printf("Unknown command: %s%n", command);
System.err.println(usage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import org.approvej.review.FileReviewResult;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -188,4 +191,129 @@ void removeLeftovers() throws IOException {
TreeMap<Path, InventoryEntry> inventory = ApprovedFileInventory.loadInventory();
assertThat(inventory).doesNotContainKey(leftoverFile).containsKey(validFile);
}

@Test
void findReceivedFiles() throws IOException {
Path approvedFile1 = tempDir.resolve("MyTest-myMethod-approved.txt");
Files.createFile(approvedFile1);
Path receivedFile1 = tempDir.resolve("MyTest-myMethod-received.txt");
Files.createFile(receivedFile1);
Path approvedFile2 = tempDir.resolve("OtherTest-other-approved.json");
Files.createFile(approvedFile2);
Path receivedFile2 = tempDir.resolve("OtherTest-other-received.json");
Files.createFile(receivedFile2);
writeString(
inventoryFile,
"# ApproveJ Approved File Inventory (auto-generated, do not edit)\n"
+ approvedFile1
+ " = com.example.MyTest#myMethod\n"
+ approvedFile2
+ " = com.example.OtherTest#other\n",
StandardOpenOption.CREATE);

List<Path> receivedFiles = ApprovedFileInventory.findReceivedFiles();

assertThat(receivedFiles)
.hasSize(2)
.anySatisfy(
p -> assertThat(p.getFileName().toString()).isEqualTo("MyTest-myMethod-received.txt"))
.anySatisfy(
p -> assertThat(p.getFileName().toString()).isEqualTo("OtherTest-other-received.json"));
}

@Test
void findReceivedFiles_only_existing() throws IOException {
Path approvedFile = tempDir.resolve("MyTest-myMethod-approved.txt");
Files.createFile(approvedFile);
Path receivedFile = tempDir.resolve("MyTest-myMethod-received.txt");
Files.createFile(receivedFile);
Path approvedFileNoReceived = tempDir.resolve("OtherTest-other-approved.json");
Files.createFile(approvedFileNoReceived);
writeString(
inventoryFile,
"# ApproveJ Approved File Inventory (auto-generated, do not edit)\n"
+ approvedFile
+ " = com.example.MyTest#myMethod\n"
+ approvedFileNoReceived
+ " = com.example.OtherTest#other\n",
StandardOpenOption.CREATE);

List<Path> receivedFiles = ApprovedFileInventory.findReceivedFiles();

assertThat(receivedFiles)
.hasSize(1)
.anySatisfy(
p -> assertThat(p.getFileName().toString()).isEqualTo("MyTest-myMethod-received.txt"));
}

@Test
void findReceivedFiles_empty() {
List<Path> receivedFiles = ApprovedFileInventory.findReceivedFiles();

assertThat(receivedFiles).isEmpty();
}

@Test
void approveAll() throws IOException {
Path receivedFile = tempDir.resolve("MyTest-myMethod-received.txt");
writeString(receivedFile, "received content", StandardOpenOption.CREATE);
Path approvedFile = tempDir.resolve("MyTest-myMethod-approved.txt");
writeString(approvedFile, "old approved content", StandardOpenOption.CREATE);
writeString(
inventoryFile,
"# ApproveJ Approved File Inventory (auto-generated, do not edit)\n"
+ approvedFile
+ " = com.example.MyTest#myMethod\n",
StandardOpenOption.CREATE);

List<Path> approved = ApprovedFileInventory.approveAll();

assertThat(approved).hasSize(1);
assertThat(receivedFile).doesNotExist();
assertThat(approvedFile).exists().hasContent("received content");
}

@Test
void approveAll_no_received_files() {
List<Path> approved = ApprovedFileInventory.approveAll();

assertThat(approved).isEmpty();
}

@Test
void reviewUnapproved() throws IOException {
Path receivedFile = tempDir.resolve("MyTest-myMethod-received.txt");
writeString(receivedFile, "received content", StandardOpenOption.CREATE);
Path approvedFile = tempDir.resolve("MyTest-myMethod-approved.txt");
writeString(approvedFile, "approved content", StandardOpenOption.CREATE);
writeString(
inventoryFile,
"# ApproveJ Approved File Inventory (auto-generated, do not edit)\n"
+ approvedFile
+ " = com.example.MyTest#myMethod\n",
StandardOpenOption.CREATE);

var reviewedProviders = new ArrayList<PathProvider>();
ApprovedFileInventory.reviewUnapproved(
pathProvider -> {
reviewedProviders.add(pathProvider);
return new FileReviewResult(false);
});

assertThat(reviewedProviders).hasSize(1);
assertThat(reviewedProviders.getFirst().receivedPath()).isEqualTo(receivedFile.normalize());
assertThat(reviewedProviders.getFirst().approvedPath()).isEqualTo(approvedFile.normalize());
}

@Test
void reviewUnapproved_no_received_files() {
var reviewedProviders = new ArrayList<PathProvider>();
ApprovedFileInventory.reviewUnapproved(
pathProvider -> {
reviewedProviders.add(pathProvider);
return new FileReviewResult(false);
});

assertThat(reviewedProviders).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,32 @@ public void apply(Project project) {
task.getMainClass().set("org.approvej.approve.ApprovedFileInventory");
task.args("--remove");
});

project
.getTasks()
.register(
"approvejApproveAll",
JavaExec.class,
task -> {
task.setGroup("verification");
task.setDescription("Approve all unapproved files");
task.setClasspath(testClasspath);
task.getMainClass().set("org.approvej.approve.ApprovedFileInventory");
task.args("--approve-all");
});

project
.getTasks()
.register(
"approvejReviewUnapproved",
JavaExec.class,
task -> {
task.setGroup("verification");
task.setDescription("Review all unapproved files");
task.setClasspath(testClasspath);
task.getMainClass().set("org.approvej.approve.ApprovedFileInventory");
task.args("--review-unapproved");
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ void apply() {

assertThat(project.getTasks().findByName("approvejFindLeftovers")).isNotNull();
assertThat(project.getTasks().findByName("approvejCleanup")).isNotNull();
assertThat(project.getTasks().findByName("approvejApproveAll")).isNotNull();
assertThat(project.getTasks().findByName("approvejReviewUnapproved")).isNotNull();
}

@Test
Expand All @@ -35,6 +37,8 @@ void apply_without_java_plugin() {

assertThat(project.getTasks().findByName("approvejFindLeftovers")).isNull();
assertThat(project.getTasks().findByName("approvejCleanup")).isNull();
assertThat(project.getTasks().findByName("approvejApproveAll")).isNull();
assertThat(project.getTasks().findByName("approvejReviewUnapproved")).isNull();
}

@Test
Expand All @@ -46,6 +50,8 @@ void apply_java_plugin_after_approvej() {

assertThat(project.getTasks().findByName("approvejFindLeftovers")).isNotNull();
assertThat(project.getTasks().findByName("approvejCleanup")).isNotNull();
assertThat(project.getTasks().findByName("approvejApproveAll")).isNotNull();
assertThat(project.getTasks().findByName("approvejReviewUnapproved")).isNotNull();
}

@Test
Expand Down Expand Up @@ -76,6 +82,34 @@ void apply_cleanup_task_configuration() {
assertThat(task.getArgs()).containsExactly("--remove");
}

@Test
void apply_approveAll_task_configuration() {
Project project = ProjectBuilder.builder().build();
project.getPluginManager().apply("java");
project.getPluginManager().apply(ApproveJPlugin.class);

var task = (JavaExec) project.getTasks().getByName("approvejApproveAll");

assertThat(task.getGroup()).isEqualTo("verification");
assertThat(task.getDescription()).isEqualTo("Approve all unapproved files");
assertThat(task.getMainClass().get()).isEqualTo("org.approvej.approve.ApprovedFileInventory");
assertThat(task.getArgs()).containsExactly("--approve-all");
}

@Test
void apply_reviewUnapproved_task_configuration() {
Project project = ProjectBuilder.builder().build();
project.getPluginManager().apply("java");
project.getPluginManager().apply(ApproveJPlugin.class);

var task = (JavaExec) project.getTasks().getByName("approvejReviewUnapproved");

assertThat(task.getGroup()).isEqualTo("verification");
assertThat(task.getDescription()).isEqualTo("Review all unapproved files");
assertThat(task.getMainClass().get()).isEqualTo("org.approvej.approve.ApprovedFileInventory");
assertThat(task.getArgs()).containsExactly("--review-unapproved");
}

@Test
void apply_functional() throws IOException {
Files.writeString(
Expand All @@ -96,7 +130,9 @@ void apply_functional() throws IOException {

assertThat(result.getOutput())
.contains("approvejFindLeftovers - List leftover approved files")
.contains("approvejCleanup - Detect and remove leftover approved files");
.contains("approvejCleanup - Detect and remove leftover approved files")
.contains("approvejApproveAll - Approve all unapproved files")
.contains("approvejReviewUnapproved - Review all unapproved files");
}

@Test
Expand All @@ -118,6 +154,8 @@ void apply_functional_without_java_plugin() throws IOException {

assertThat(result.getOutput())
.doesNotContain("approvejFindLeftovers")
.doesNotContain("approvejCleanup");
.doesNotContain("approvejCleanup")
.doesNotContain("approvejApproveAll")
.doesNotContain("approvejReviewUnapproved");
}
}
Loading