diff options
author | Simeon Andreev | 2020-06-30 11:00:22 +0000 |
---|---|---|
committer | Andrey Loskutov | 2020-07-03 16:17:01 +0000 |
commit | 03c0989a5bc07e20793e344c6ad788f76b8de370 (patch) | |
tree | 3196ecdfb44bc87c94c5029449207001050d5791 | |
parent | 79f63ad5bfa3069919490bbf6567f3c766e5f2e8 (diff) | |
download | eclipse.jdt.core-03c0989a5bc07e20793e344c6ad788f76b8de370.tar.gz eclipse.jdt.core-03c0989a5bc07e20793e344c6ad788f76b8de370.tar.xz eclipse.jdt.core-03c0989a5bc07e20793e344c6ad788f76b8de370.zip |
Bug 564905 - [regression] Missing full build on .class file changesI20200705-1800I20200705-0710I20200705-0600I20200704-1800I20200704-0600I20200703-1800
If the output folder of a project is deleted in file system, while the
project is closed, the project is not built on re-open/refresh. The same
is true if Eclipse was closed when deleting the output folder (e.g. with
a git clean).
This is a regression caused by a fix for bug 563030. When
State.typeLocators is persisted, instead of writing key/value pairs,
value/value pairs are written. As a result, after persisting the project
build state, IncrementalImageBuilder.checkForClassFileChanges() no
longer detects .class file changes. It looks with keys in
State.typeLocators, while the map has value->value pairs.
Change-Id: I154764626d7a6ff264c0d3d4931d1689df60878e
Signed-off-by: Simeon Andreev <simeon.danailov.andreev@gmail.com>
4 files changed, 212 insertions, 2 deletions
diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug564905Test.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug564905Test.java new file mode 100644 index 0000000000..86f54d2d5c --- /dev/null +++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug564905Test.java @@ -0,0 +1,202 @@ +/******************************************************************************* + * Copyright (c) 2020 Simeon Andreev and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.core.tests.builder; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.tests.util.Util; +import junit.framework.Test; + +public class Bug564905Test extends BuilderTests { + + private static final String OUTPUT_FOLDER_NAME = "bin"; + + private String projectName; + private IProject project; + private IPath projectPath; + private IPath src; + private IFolder outputFolder; + private boolean oldAutoBuilding; + + public Bug564905Test(String name) { + super(name); + } + + public static Test suite() { + return buildTestSuite(Bug564905Test.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + this.projectName = "Bug564905Test"; + this.projectPath = env.addProject(this.projectName); + this.project = env.getWorkspace().getRoot().getProject(this.projectName); + env.addExternalJars(this.projectPath, Util.getJavaClassLibs()); + + env.removePackageFragmentRoot(this.projectPath, ""); + String outputFolderPath = "tmp/" + OUTPUT_FOLDER_NAME; + this.src = env.addPackageFragmentRoot(this.projectPath, "src", null, outputFolderPath); + this.outputFolder = env.getWorkspace().getRoot().getFolder(this.projectPath.append(outputFolderPath)); + + this.oldAutoBuilding = env.isAutoBuilding(); + env.setAutoBuilding(true); + waitForAutoBuild(); + } + + @Override + protected void tearDown() throws Exception { + TestBuilderParticipant.PARTICIPANT = null; + env.removeProject(this.projectPath); + env.setAutoBuilding(this.oldAutoBuilding); + waitForAutoBuild(); + + super.tearDown(); + } + + /** + * Test for Bug 564905, with option {@code org.eclipse.jdt.core.builder.recreateModifiedClassFileInOutputFolder} enabled. + * + * When the output folder of a project is removed in file system, on refresh we expect a build. + */ + public void testBug564905_recreateModifiedClassFileInOutputFolder_enabled() throws Exception { + enableOption_recreateModifiedClassFileInOutputFolder(); + assertOutputFolderEmpty(); + + addSourceAndBuild(); + assertOutputFolderNotEmpty(); + + deleteOutputFolderAndWaitForAutoBuild(); + // we enabled "recreateModifiedClassFileInOutputFolder", so we expect compile artifacts + assertOutputFolderNotEmpty(); + } + + /** + * Test for Bug 564905, with option {@code org.eclipse.jdt.core.builder.recreateModifiedClassFileInOutputFolder} disabled. + * + * When the output folder of a project is removed in file system, on refresh we don't expect a build + * as we don't use the option {@link JavaCore#CORE_JAVA_BUILD_RECREATE_MODIFIED_CLASS_FILES_IN_OUTPUT_FOLDER}. + */ + public void testBug564905_recreateModifiedClassFileInOutputFolder_disabled() throws Exception { + disableOption_recreateModifiedClassFileInOutputFolder(); + assertOutputFolderEmpty(); + + addSourceAndBuild(); + assertOutputFolderNotEmpty(); + + deleteOutputFolderAndWaitForAutoBuild(); + // we disabled "recreateModifiedClassFileInOutputFolder", so we don't expect compile artifacts + assertOutputFolderEmpty(); + } + + private void deleteOutputFolderAndWaitForAutoBuild() throws Exception { + // close the project, since the bug 564905 occurs when build state is read from disk + this.project.close(new NullProgressMonitor()); + waitForAutoBuild(); + URI outputFolderUri = this.outputFolder.getLocationURI(); + // delete the output folder with file system API, so that Eclipse resources API "doesn't notice" + deleteFolderInFileSystem(outputFolderUri); + + // re-open the project, refresh it, then wait for auto-build; expect that something was built + this.project.open(new NullProgressMonitor()); + this.project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + waitForAutoBuild(); + } + + private void addSourceAndBuild() { + IPath srcPackage = env.addPackage(this.src, "p"); + IFolder srcPackageFolder = env.getWorkspace().getRoot().getFolder(srcPackage); + assertTrue("package in source must exist", srcPackageFolder.exists()); + env.addClass(this.src, "p", "X", "package p;\n public interface X { void foo() { /* we want something compiled, anything works */ } }"); + fullBuild(this.projectPath); + } + + private void enableOption_recreateModifiedClassFileInOutputFolder() throws Exception { + setJavaProjectOption(JavaCore.CORE_JAVA_BUILD_RECREATE_MODIFIED_CLASS_FILES_IN_OUTPUT_FOLDER, JavaCore.ENABLED); + } + + private void disableOption_recreateModifiedClassFileInOutputFolder() throws Exception { + setJavaProjectOption(JavaCore.CORE_JAVA_BUILD_RECREATE_MODIFIED_CLASS_FILES_IN_OUTPUT_FOLDER, JavaCore.DISABLED); + } + + private void setJavaProjectOption(String optionName, String optionValue) throws Exception { + IJavaProject javaProject = JavaCore.create(this.project); + javaProject.setOption(optionName, optionValue); + this.project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + waitForAutoBuild(); + } + + private void waitForAutoBuild() throws InterruptedException { + Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD, new NullProgressMonitor()); + } + + private static void deleteFolderInFileSystem(URI uri) throws IOException { + Files.walkFileTree(Paths.get(uri), new DeleteVisitor()); + } + + private void assertOutputFolderEmpty() throws CoreException { + assertTrue("output folder must exist", this.outputFolder.exists()); + IResource[] outputFolderContent = this.outputFolder.members(); + assertEquals("output folder must be empty, instead had contents: " + toString(outputFolderContent), 0, outputFolderContent.length); + } + + private void assertOutputFolderNotEmpty() throws CoreException { + assertTrue("output folder must exist", this.outputFolder.exists()); + assertTrue("output folder must not be empty", this.outputFolder.members().length > 0); + } + + private static String toString(IResource[] resources) { + StringBuilder result = new StringBuilder(); + for (IResource resource : resources) { + result.append(resource.getName()); + result.append(System.lineSeparator()); + } + return result.toString(); + } + + static class DeleteVisitor extends SimpleFileVisitor<Path> { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + if (exc != null) { + throw exc; + } + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + } +} diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BuilderTests.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BuilderTests.java index fbb210a593..1938645b43 100644 --- a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BuilderTests.java +++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BuilderTests.java @@ -548,6 +548,7 @@ public class BuilderTests extends TestCase { Bug530366Test.class, Bug531382Test.class, Bug549457Test.class, + Bug564905Test.class, Bug561287Test.class, Bug562420Test.class, LeakTestsBefore9.class, diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/StateTest.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/StateTest.java index f3ddd9a730..fed6380618 100644 --- a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/StateTest.java +++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/StateTest.java @@ -120,6 +120,13 @@ public class StateTest extends BuilderTests { State readState = JavaBuilder.readState(project, new DataInputStream(new ByteArrayInputStream(bytes))); Map<String, ReferenceCollection> readReferences = readState.getReferences(); assertEqualLookupTables(savedState.getReferences(), readReferences); + assertEqualTypeLocators(savedState.typeLocators, readState.typeLocators); + } + + private void assertEqualTypeLocators(Map<String, String> tl1, Map<String, String> tl2) { + assertEquals(tl1.size(), tl2.size()); + assertEquals(tl1.toString(), tl2.toString()); + } private void assertEqualLookupTables(Map<String, ReferenceCollection> expectation, Map<String, ReferenceCollection> actual) { diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/State.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/State.java index 737ad74602..4447a90877 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/State.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/State.java @@ -153,7 +153,7 @@ public boolean isKnownPackage(String qualifiedPackageName) { LinkedHashSet<String> names = new LinkedHashSet<>(this.typeLocators.size()); Set<Entry<String, String>> keyTable = this.typeLocators.entrySet(); for (Entry<String, String> entry : keyTable) { - String packageName = entry.getValue(); // is a type name of the form p1/p2/A + String packageName = entry.getKey(); // is a type name of the form p1/p2/A int last = packageName.lastIndexOf('/'); packageName = last == -1 ? null : packageName.substring(0, last); while (packageName != null && !names.contains(packageName)) { @@ -756,7 +756,7 @@ void write(DataOutputStream out) throws IOException { String value = entry.getValue(); if (key != null) { length--; - out.writeUTF(value); + out.writeUTF(key); Integer index = (Integer) internedTypeLocators.get(value); out.writeInt(index.intValue()); } |