From 3c4d2f6ef7e704ffb3843b8366718926dd039af2 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Tue, 2 Jan 2024 15:38:04 +0100 Subject: [PATCH] Revert "Add `detach-history` command to the ContentGenerator tool (#7867)" (#7907) This reverts commit b07a1fee1bee5b24618e2f5da274370313fce47f. --- .../contentgenerator/ITDetachHistory.java | 184 ----------------- .../contentgenerator/ITRefreshContent.java | 49 +++-- .../cli/ContentGenerator.java | 1 - .../contentgenerator/cli/DetachHistory.java | 190 ------------------ .../contentgenerator/cli/RefreshContent.java | 2 +- .../AbstractContentGeneratorTest.java | 29 --- .../contentgenerator/RunContentGenerator.java | 14 +- 7 files changed, 39 insertions(+), 430 deletions(-) delete mode 100644 tools/content-generator/src/intTest/java/org/projectnessie/tools/contentgenerator/ITDetachHistory.java delete mode 100644 tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/DetachHistory.java diff --git a/tools/content-generator/src/intTest/java/org/projectnessie/tools/contentgenerator/ITDetachHistory.java b/tools/content-generator/src/intTest/java/org/projectnessie/tools/contentgenerator/ITDetachHistory.java deleted file mode 100644 index 8bb3086898c..00000000000 --- a/tools/content-generator/src/intTest/java/org/projectnessie/tools/contentgenerator/ITDetachHistory.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2022 Dremio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.projectnessie.tools.contentgenerator; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.projectnessie.tools.contentgenerator.RunContentGenerator.runGeneratorCmd; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.projectnessie.client.api.NessieApiV2; -import org.projectnessie.error.NessieConflictException; -import org.projectnessie.error.NessieNotFoundException; -import org.projectnessie.model.Branch; -import org.projectnessie.model.Content; -import org.projectnessie.model.ContentKey; -import org.projectnessie.model.IcebergTable; -import org.projectnessie.model.Reference; -import org.projectnessie.tools.contentgenerator.RunContentGenerator.ProcessResult; - -class ITDetachHistory extends AbstractContentGeneratorTest { - - private static final ContentKey key1 = ContentKey.of("test", "key1"); - private static final ContentKey key2 = ContentKey.of("test", "key2"); - private static final ContentKey key3 = ContentKey.of("test_key3"); - private static final IcebergTable table1 = IcebergTable.of("meta_111", 1, 2, 3, 4); - private static final IcebergTable table2 = IcebergTable.of("meta_222", 1, 2, 3, 4); - private static final IcebergTable table3 = IcebergTable.of("meta_333", 1, 2, 3, 4); - - private NessieApiV2 api; - - @BeforeEach - void setUp() { - api = buildNessieApi(); - } - - private void runMain(String... args) { - runMain(s -> {}, args); - } - - private ProcessResult runMain(Consumer stdoutWatcher, String... args) { - List allArgs = new ArrayList<>(); - allArgs.add("detach-history"); - allArgs.add("--uri"); - allArgs.add(NESSIE_API_URI); - allArgs.addAll(Arrays.asList(args)); - - ProcessResult result = runGeneratorCmd(stdoutWatcher, allArgs.toArray(new String[0])); - assertThat(result) - .describedAs(result.toString()) - .extracting(ProcessResult::getExitCode) - .isEqualTo(0); - return result; - } - - private Content contentWithoutId(String ref, ContentKey key) throws NessieNotFoundException { - return get(api, ref, key).withId(null); - } - - @Test - void severalBranchesAllContent() throws NessieNotFoundException, NessieConflictException { - Branch main = api.getDefaultBranch(); - Branch test = - (Branch) api.createReference().reference(Branch.of("test1", main.getHash())).create(); - main = create(api, main, table1, key1); - main = create(api, main, table2, key2); - test = create(api, test, table1, key1); - test = create(api, test, table3, key3); - - runMain("--all", "--src-suffix", "-src123", "--final-suffix", "-done123"); - - Reference mainOrig = api.getReference().refName(main.getName() + "-src123").get(); - assertThat(mainOrig.getHash()).isEqualTo(main.getHash()); - Reference mainCompleted = api.getReference().refName(main.getName() + "-done123").get(); - assertThat(mainCompleted.getHash()).isEqualTo(main.getHash()); - Reference testOrig = api.getReference().refName(test.getName() + "-src123").get(); - assertThat(testOrig.getHash()).isEqualTo(test.getHash()); - Reference testCompleted = api.getReference().refName(test.getName() + "-done123").get(); - assertThat(testCompleted.getHash()).isEqualTo(test.getHash()); - - assertThat(contentWithoutId(main.getName(), key1)).isEqualTo(table1); - assertThat(contentWithoutId(main.getName(), key2)).isEqualTo(table2); - assertThat(contentWithoutId(test.getName(), key1)).isEqualTo(table1); - assertThat(contentWithoutId(test.getName(), key3)).isEqualTo(table3); - - assertThat(contentWithoutId(main.getName(), key1.getParent())).isEqualTo(key1.getNamespace()); - } - - @Test - void mainBatched() throws NessieNotFoundException, NessieConflictException { - Branch main = api.getDefaultBranch(); - int numTables = 200; - for (int i = 1; i < numTables; i++) { - main = create(api, main, table1, ContentKey.of("test-" + i)); - } - - runMain("--all", "--batch", "7"); - - Reference mainOrig = api.getReference().refName(main.getName() + "-original").get(); - assertThat(mainOrig.getHash()).isEqualTo(main.getHash()); - Reference mainCompleted = api.getReference().refName(main.getName() + "-completed").get(); - assertThat(mainCompleted.getHash()).isEqualTo(main.getHash()); - - for (int i = 1; i < numTables; i++) { - assertThat(contentWithoutId(main.getName(), ContentKey.of("test-" + i))).isEqualTo(table1); - } - } - - @Test - void retries() throws NessieNotFoundException, NessieConflictException { - Branch root = api.getDefaultBranch(); - // This branch will have concurrent changes - AtomicReference main = new AtomicReference<>(root); - main.set(create(api, main.get(), table1, key1)); - - // This branch will be stable and will migrate in the first attempt - Branch test = - (Branch) api.createReference().reference(Branch.of("test1", root.getHash())).create(); - test = create(api, test, table1, key1); - - AtomicInteger count = new AtomicInteger(); - Consumer stdoutWatcher = - text -> { - if (text.contains("Reassigning references")) { - // Simulate some concurrent changes - try { - int c = count.getAndIncrement(); - if (c == 0) { - main.set(create(api, api.getDefaultBranch(), table2, key2)); - } else if (c == 1) { - main.set(create(api, api.getDefaultBranch(), table3, key3)); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }; - - ProcessResult result = runMain(stdoutWatcher, "--all"); - - assertThat(count.get()).isEqualTo(3); // 2 failed attempts + 1 successful - - Reference mainOrig = api.getReference().refName(main.get().getName() + "-original").get(); - assertThat(mainOrig.getHash()).isEqualTo(main.get().getHash()); - Reference mainCompleted = api.getReference().refName(main.get().getName() + "-completed").get(); - assertThat(mainCompleted.getHash()).isEqualTo(main.get().getHash()); - - assertThat(contentWithoutId(api.getDefaultBranch().getName(), key1)).isEqualTo(table1); - assertThat(contentWithoutId(api.getDefaultBranch().getName(), key2)).isEqualTo(table2); - assertThat(contentWithoutId(api.getDefaultBranch().getName(), key3)).isEqualTo(table3); - - assertThat(api.getDefaultBranch().getHash()).isNotEqualTo(main.get().getHash()); - - Reference testOrig = api.getReference().refName(test.getName() + "-original").get(); - assertThat(testOrig.getHash()).isEqualTo(test.getHash()); - Reference testCompleted = api.getReference().refName(test.getName() + "-completed").get(); - assertThat(testCompleted.getHash()).isEqualTo(test.getHash()); - - assertThat(api.getReference().refName(test.getName()).get().getHash()) - .isNotEqualTo(test.getHash()); - - // Temporary references should not be migrated. - assertThat(result.getStdOutLines()) - .noneSatisfy(l -> assertThat(l).matches("Completed migration.*-tmp-.*")); - } -} diff --git a/tools/content-generator/src/intTest/java/org/projectnessie/tools/contentgenerator/ITRefreshContent.java b/tools/content-generator/src/intTest/java/org/projectnessie/tools/contentgenerator/ITRefreshContent.java index a92d05d28aa..a30a93bea41 100644 --- a/tools/content-generator/src/intTest/java/org/projectnessie/tools/contentgenerator/ITRefreshContent.java +++ b/tools/content-generator/src/intTest/java/org/projectnessie/tools/contentgenerator/ITRefreshContent.java @@ -35,8 +35,10 @@ import org.junit.jupiter.params.provider.ValueSource; import org.projectnessie.client.api.NessieApiV2; import org.projectnessie.error.NessieConflictException; +import org.projectnessie.error.NessieNamespaceAlreadyExistsException; import org.projectnessie.error.NessieNotFoundException; import org.projectnessie.model.Branch; +import org.projectnessie.model.CommitMeta; import org.projectnessie.model.ContentKey; import org.projectnessie.model.FetchOption; import org.projectnessie.model.IcebergTable; @@ -63,8 +65,31 @@ void setUp() { api = buildNessieApi(); } + private void create(IcebergTable table, ContentKey key) + throws NessieConflictException, NessieNotFoundException { + if (key.getElementCount() > 1) { + try { + api.createNamespace() + .namespace(key.getNamespace()) + .reference(api.getDefaultBranch()) + .create(); + } catch (NessieNamespaceAlreadyExistsException ignore) { + // + } + } + api.commitMultipleOperations() + .branch(api.getDefaultBranch()) + .commitMeta(CommitMeta.fromMessage("test-commit")) + .operation(Operation.Put.of(key, table)) + .commit(); + } + private IcebergTable get(ContentKey key) throws NessieNotFoundException { - return (IcebergTable) get(api, "main", key); + return get("main", key); + } + + private IcebergTable get(String refName, ContentKey key) throws NessieNotFoundException { + return (IcebergTable) api.getContent().refName(refName).key(key).get().get(key); } private List log(int depth) throws NessieNotFoundException { @@ -89,7 +114,7 @@ private int runMain(String... args) { @Test void refreshOneKey() throws NessieNotFoundException, NessieConflictException { - create(api, table1, key1); + create(table1, key1); IcebergTable stored1 = get(key1); assertThat( @@ -122,7 +147,7 @@ void refreshEmptyBatch() throws NessieNotFoundException { @Test void failOnExplicitTagArgument() throws NessieNotFoundException, NessieConflictException { - create(api, table3, key3); + create(table3, key3); IcebergTable stored3 = get(key3); String tagName = "testTag_" + UUID.randomUUID(); @@ -156,9 +181,9 @@ private void writeInputFile(File file, ContentKey... keys) throws IOException { @ParameterizedTest @ValueSource(ints = {1, 2, 3, 4, 100}) void refreshFromFile(int batchSize, @TempDir File tempDir) throws IOException { - create(api, table1, key1); - create(api, table2, key2); - create(api, table3, key3); + create(table1, key1); + create(table2, key2); + create(table3, key3); IcebergTable stored1 = get(key1); IcebergTable stored2 = get(key2); IcebergTable stored3 = get(key3); @@ -196,8 +221,8 @@ void refreshFromFile(int batchSize, @TempDir File tempDir) throws IOException { } @Test - void partialRefreshFromFileWithRefOverride(@TempDir File tempDir) throws IOException { - create(api, table1, key1); + void partialRefreshFromFileWithRefOveride(@TempDir File tempDir) throws IOException { + create(table1, key1); IcebergTable stored1 = get(key1); Branch main = api.getDefaultBranch(); @@ -230,15 +255,15 @@ void partialRefreshFromFileWithRefOverride(@TempDir File tempDir) throws IOExcep .collect(Collectors.toList())) .containsExactly(key1); // Note: key2 is not present - assertThat(get(api, ref.getName(), key1)).isEqualTo(stored1); + assertThat(get(ref.getName(), key1)).isEqualTo(stored1); } @ParameterizedTest @ValueSource(ints = {1, 2, 3, 4, 5, 6, 100}) void refreshAllKeys(int batchSize) throws IOException { - create(api, table1, key1); - create(api, table2, key2); - create(api, table3, key3); + create(table1, key1); + create(table2, key2); + create(table3, key3); IcebergTable stored1 = get(key1); IcebergTable stored2 = get(key2); IcebergTable stored3 = get(key3); diff --git a/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/ContentGenerator.java b/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/ContentGenerator.java index f6454fa1243..72630fad4ba 100644 --- a/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/ContentGenerator.java +++ b/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/ContentGenerator.java @@ -37,7 +37,6 @@ ReadReferences.class, ReadContent.class, RefreshContent.class, - DetachHistory.class, DeleteContent.class, CreateMissingNamespaces.class, CommandLine.HelpCommand.class diff --git a/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/DetachHistory.java b/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/DetachHistory.java deleted file mode 100644 index 31f741434f8..00000000000 --- a/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/DetachHistory.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2023 Dremio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.projectnessie.tools.contentgenerator.cli; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import org.projectnessie.client.api.NessieApiV2; -import org.projectnessie.error.BaseNessieClientServerException; -import org.projectnessie.error.NessieConflictException; -import org.projectnessie.error.NessieNotFoundException; -import org.projectnessie.error.NessieReferenceConflictException; -import org.projectnessie.model.Branch; -import org.projectnessie.model.Content; -import org.projectnessie.model.ContentKey; -import org.projectnessie.model.Reference; -import org.projectnessie.model.Tag; -import picocli.CommandLine; -import picocli.CommandLine.Command; - -@Command( - name = "detach-history", - mixinStandardHelpOptions = true, - description = - "Get the latest contents from one or all branches, commit it directly on top of the root commit (no history), " - + "then reassign the original branch(es) to the new commit.") -public class DetachHistory extends RefreshContent { - - @CommandLine.Option( - names = {"--src-suffix"}, - description = - "Name suffix for tagging the original (source) branches, defaults to '-original'.") - private String origSuffix = "-original"; - - @CommandLine.Option( - names = {"--final-suffix"}, - description = - "Name suffix for tagging the the final stat of source branches (equivalent to the tag generated " - + "from the --src-suffix option). This suffix is used during retries, defaults to '-completed'.") - private String completedSuffix = "-completed"; - - @CommandLine.Option( - names = {"--max-attempts"}, - description = - "Max attempts (in case of concurrent commits from other actors), defaults to 100.") - private int maxAttempts = 100; - - private final Map sources = new HashMap<>(); - private final Map targets = new HashMap<>(); - private String tmpSuffix; - private String rootHash; - - @Override - public void execute() throws BaseNessieClientServerException { - // Overview: - // 1) Tag old source branch HEADs (in setup()). - // 2) Copy the latest content to temporary branches. - // 3) Reassign source branches to HEADs of corresponding temporary branches. - // The last step may fail if there are concurrent commits to source branch, in which case - // the whole process is re-tried. - try (NessieApiV2 api = createNessieApiInstance()) { - rootHash = api.getConfig().getNoAncestorHash(); - for (int attempt = 0; attempt < maxAttempts; attempt++) { - spec.commandLine().getOut().printf("Running attempt %d...%n", attempt); - - sources.clear(); - targets.clear(); - tmpSuffix = "-tmp-" + UUID.randomUUID(); - - super.execute(); // copy content into temporary branches - - try { - // reassign original branch names to HEADs of corresponding temporary branches - reassign(api); - - spec.commandLine().getOut().printf("Completed successfully%n"); - break; - } catch (NessieReferenceConflictException e) { - spec.commandLine() - .getOut() - .printf("Unable to complete attempt %d, retrying...%n", attempt); - - // Delete any remaining temporary references - for (Branch target : targets.values()) { - try { - Reference tmpRef = api.getReference().refName(target.getName()).get(); - api.deleteReference().reference(tmpRef).delete(); - } catch (NessieNotFoundException ex) { - // ignore, the target reference must have been reassigned and deleted - } - } - } - } - } - } - - @Override - protected void commitSameContent( - NessieApiV2 api, Branch source, Map contentMap) - throws BaseNessieClientServerException { - Branch target = setup(api, source); - if (target == null) { - return; // already processed - } - - Map detachedContents = new HashMap<>(); - // Remove content IDs because these entries will be treated as new entities - // on the target branch due to empty commit history there. - contentMap.forEach((k, v) -> detachedContents.put(k, v.withId(null))); - super.commitSameContent(api, target, detachedContents); - } - - private Branch setup(NessieApiV2 api, Branch source) - throws NessieConflictException, NessieNotFoundException { - Branch old = sources.putIfAbsent(source.getName(), source); - if (old != null && !Objects.equals(old.getHash(), source.getHash())) { - throw new IllegalArgumentException("Hash mismatch"); - } - - try { - api.getReference().refName(source.getName() + completedSuffix).get(); - spec.commandLine().getOut().printf("Ignoring previously migrated reference '%s'%n", source); - return null; - } catch (NessieNotFoundException e) { - // expected - } - - spec.commandLine().getOut().printf("Migrating '%s'%n", source); - String origName = source.getName() + origSuffix; - try { - Reference origRef = api.getReference().refName(origName).get(); - api.assignReference().reference(origRef).assignTo(source).assign(); - } catch (NessieNotFoundException e) { - api.createReference() - .sourceRefName(source.getName()) - .reference(Tag.of(origName, source.getHash())) - .create(); - } - - return targets.computeIfAbsent( - source.getName(), - sourceName -> { - String targetName = sourceName + tmpSuffix; - try { - // create a target branch without any commit history - return (Branch) - api.createReference().reference(Branch.of(targetName, rootHash)).create(); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - private void reassign(NessieApiV2 api) throws NessieNotFoundException, NessieConflictException { - spec.commandLine().getOut().println("Reassigning references..."); - - for (Map.Entry e : targets.entrySet()) { - String name = e.getKey(); - Reference target = e.getValue(); - Branch source = sources.get(name); - - // get the latest hash - target = api.getReference().refName(target.getName()).get(); - - api.assignReference().reference(source).assignTo(target).assign(); - - api.createReference() - .reference(Tag.of(source.getName() + completedSuffix, source.getHash())) - .create(); - - spec.commandLine().getOut().printf("Completed migration for '%s'%n", source.getName()); - - api.deleteReference().reference(target).delete(); - } - } -} diff --git a/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/RefreshContent.java b/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/RefreshContent.java index 0819390d8a6..75168e6b5e3 100644 --- a/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/RefreshContent.java +++ b/tools/content-generator/src/main/java/org/projectnessie/tools/contentgenerator/cli/RefreshContent.java @@ -47,7 +47,7 @@ protected void processBatch(NessieApiV2 api, Branch ref, List keys) } } - protected void commitSameContent( + private void commitSameContent( NessieApiV2 api, Branch branch, Map contentMap) throws BaseNessieClientServerException { diff --git a/tools/content-generator/src/testFixtures/java/org/projectnessie/tools/contentgenerator/AbstractContentGeneratorTest.java b/tools/content-generator/src/testFixtures/java/org/projectnessie/tools/contentgenerator/AbstractContentGeneratorTest.java index a5736b16b4a..d16e934af1f 100644 --- a/tools/content-generator/src/testFixtures/java/org/projectnessie/tools/contentgenerator/AbstractContentGeneratorTest.java +++ b/tools/content-generator/src/testFixtures/java/org/projectnessie/tools/contentgenerator/AbstractContentGeneratorTest.java @@ -23,11 +23,9 @@ import org.junit.jupiter.api.BeforeEach; import org.projectnessie.client.api.NessieApiV2; import org.projectnessie.error.NessieConflictException; -import org.projectnessie.error.NessieNamespaceAlreadyExistsException; import org.projectnessie.error.NessieNotFoundException; import org.projectnessie.model.Branch; import org.projectnessie.model.CommitMeta; -import org.projectnessie.model.Content; import org.projectnessie.model.ContentKey; import org.projectnessie.model.Detached; import org.projectnessie.model.IcebergTable; @@ -94,33 +92,6 @@ protected Branch makeCommit(NessieApiV2 api) .commit(); } - protected Content get(NessieApiV2 api, String refName, ContentKey key) - throws NessieNotFoundException { - return api.getContent().refName(refName).key(key).get().get(key); - } - - protected void create(NessieApiV2 api, IcebergTable table, ContentKey key) - throws NessieNotFoundException, NessieConflictException { - create(api, api.getDefaultBranch(), table, key); - } - - protected Branch create(NessieApiV2 api, Branch branch, IcebergTable table, ContentKey key) - throws NessieNotFoundException, NessieConflictException { - if (key.getElementCount() > 1) { - try { - api.createNamespace().namespace(key.getNamespace()).reference(branch).create(); - } catch (NessieNamespaceAlreadyExistsException ignore) { - // - } - } - - return api.commitMultipleOperations() - .branch(branch) - .commitMeta(CommitMeta.fromMessage("test-commit")) - .operation(Operation.Put.of(key, table)) - .commit(); - } - protected NessieApiV2 buildNessieApi() { return createClientBuilderFromSystemSettings().withUri(NESSIE_API_URI).build(NessieApiV2.class); } diff --git a/tools/content-generator/src/testFixtures/java/org/projectnessie/tools/contentgenerator/RunContentGenerator.java b/tools/content-generator/src/testFixtures/java/org/projectnessie/tools/contentgenerator/RunContentGenerator.java index 4ee7609a0ab..4a80a154e11 100644 --- a/tools/content-generator/src/testFixtures/java/org/projectnessie/tools/contentgenerator/RunContentGenerator.java +++ b/tools/content-generator/src/testFixtures/java/org/projectnessie/tools/contentgenerator/RunContentGenerator.java @@ -19,7 +19,6 @@ import java.io.StringWriter; import java.util.Arrays; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Collectors; import org.projectnessie.tools.contentgenerator.cli.NessieContentGenerator; @@ -27,20 +26,9 @@ public final class RunContentGenerator { private RunContentGenerator() {} public static ProcessResult runGeneratorCmd(String... params) { - return runGeneratorCmd(s -> {}, params); - } - - public static ProcessResult runGeneratorCmd(Consumer stdoutWatcher, String... params) { try (StringWriter stringOut = new StringWriter(); StringWriter stringErr = new StringWriter(); - PrintWriter out = - new PrintWriter(stringOut) { - @Override - public void write(String s, int off, int len) { - stdoutWatcher.accept(s.substring(off, off + len)); - super.write(s, off, len); - } - }; + PrintWriter out = new PrintWriter(stringOut); PrintWriter err = new PrintWriter(stringErr)) { int exitCode = NessieContentGenerator.runMain(out, err, params); String output = stringOut.toString();