Permalink
Browse files

re #1000631. Adding a classpath validator. Initial commit. Breaks Sbt…

…BuilderTest@dependencyTest.

Added validator methods to ScalaProject.
Added link for Java Model listener to validator when classpath is modified in ScalaPlugin.
Added check if project classpath is valid in ScalaBuilder.
Added set of tests for different configuration of the classpath.
  • Loading branch information...
1 parent 17cdf11 commit 809cf3ba02331dd03fe1c715e4296b1b47cfd645 @skyluc skyluc committed Oct 11, 2011
@@ -1,5 +1,6 @@
package scala.tools.eclipse;
+import scala.tools.eclipse.classpath.ClasspathTests;
import scala.tools.eclipse.completion.CompletionTests;
import scala.tools.eclipse.hyperlinks.HyperlinkDetectorTests;
@@ -33,6 +34,7 @@
CompletionTests.class,
AbstractMethodVerifierTest.class,
SbtBuilderTest.class,
- PCTest.class
+ PCTest.class,
+ ClasspathTests.class
})
class TestsSuite { }
@@ -0,0 +1,244 @@
+package scala.tools.eclipse.classpath
+
+import scala.tools.eclipse.testsetup.TestProjectSetup
+import org.junit.Assert._
+import org.junit.Test
+import org.eclipse.jdt.core.JavaCore
+import org.eclipse.core.resources.IResource
+import org.eclipse.core.resources.IncrementalProjectBuilder
+import org.eclipse.core.runtime.NullProgressMonitor
+import org.eclipse.core.runtime.Path
+import org.junit.Before
+import org.eclipse.jdt.core.IClasspathEntry
+import org.eclipse.core.resources.IMarker
+import scala.tools.eclipse.ScalaPlugin
+import org.junit.After
+import org.junit.Ignore
+
+object ClasspathTests extends TestProjectSetup("classpath")
+
+class ClasspathTests {
+
+ import ClasspathTests._
+
+ /**
+ * The default classpath, with the eclipse scala container.
+ */
+ val baseRawClasspath= project.javaProject.getRawClasspath()
+
+ /**
+ * The classpath, with the eclipse scala container removed.
+ */
+ def cleanRawClasspath= for (classpathEntry <- baseRawClasspath
+ if classpathEntry.getPath().toPortableString() != "org.scala-ide.sdt.launching.SCALA_CONTAINER")
+ yield classpathEntry
+
+ @After
+ def resetClasspath() {
+ setRawClasspathAndCheckMarkers(baseRawClasspath, 0, 0)
+ }
+
+ /**
+ * The scala library is defined as part of the eclipse container in the classpath (default case)
+ */
+ @Test
+ def eclipseContainerScalaLibrary() {
+ setRawClasspathAndCheckMarkers(baseRawClasspath, 0, 0)
+ }
+
+ /**
+ * No scala library defined in the classpath
+ */
+ @Test
+ def noScalaLibrary() {
+ setRawClasspathAndCheckMarkers(cleanRawClasspath, 0, 1)
+ }
+
+ /**
+ * Two scala library defined in the classpath, the eclipse container one, and one from the lib folder
+ */
+ @Test
+ def twoScalaLibraries() {
+ setRawClasspathAndCheckMarkers(baseRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/2.10.x/scala-library.jar"), null, null), 0, 1)
+ }
+
+ /**
+ * Two scala library defined in the classpath, the eclipse container one, and one with a different name.
+ */
+ @Test
+ def twoScalaLibrariesWithDifferentName() {
+ setRawClasspathAndCheckMarkers(baseRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/2.10.x/my-scala-library.jar"), null, null), 0, 1)
+ }
+
+ /**
+ * The scala library is defined using a classpath variable, with a different but compatible version
+ */
+ @Test
+ def usingClasspathVariable() {
+ // create a classpath variable
+ JavaCore.setClasspathVariable("CLASSPATH_TEST_LIB", new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/"), new NullProgressMonitor)
+ setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newVariableEntry(new Path("CLASSPATH_TEST_LIB/scala-library.jar"), null, null), 1, 0)
+ }
+
+ /**
+ * The scala-library.jar from the lib folder is marked as being a different version, but compatible
+ */
+ @Test
+ def differentButCompatibleVersion() {
+ setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/scala-library.jar"), null, null), 1, 0)
+ }
+
+ /**
+ * The scala-library.jar is marked as being a different, incompatible version
+ */
+ @Test
+ def differentAndIncompatibleVersion() {
+ val newRawClasspath= cleanRawClasspath :+
+ JavaCore.newLibraryEntry(new Path("/classpath/lib/" +
+ (ScalaPlugin.plugin.shortScalaVer match {
+ case "2.8" => "2.9"
+ case "2.9" => "2.10"
+ case "2.10" => "2.8"
+ case _ =>
+ fail("Unsupported embedded scala library version " + ScalaPlugin.plugin.scalaVer +". Please update the test.")
+ ""
+ }) + ".x/scala-library.jar"), null, null)
+
+ setRawClasspathAndCheckMarkers(newRawClasspath, 0, 1)
+ }
+
+ /**
+ * The properties file in scala-library.jar doesn't contain the version information
+ */
+ @Test
+ def noVersionInPropertiesFile() {
+ setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/noversion/scala-library.jar"), null, null), 0, 1)
+ }
+
+ /**
+ * The scala-library.jar doesn't contain a properties file.
+ */
+ @Test
+ def noPropertiesFile() {
+ setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/noproperties/scala-library.jar"), null, null), 0, 1)
+ }
+
+ /**
+ * The library has a different name, but with a compatible version and contains scala.Predef
+ */
+ @Test
+ def differentNameWithCompatibleVersion() {
+ setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/my-scala-library.jar"), null, null), 1, 0)
+ }
+
+ /**
+ * The library has a different name, but with a compatible version and contains scala.Predef
+ */
+ @Test
+ def differentNameWithIncompatibleVersion() {
+ val newRawClasspath= cleanRawClasspath :+
+ JavaCore.newLibraryEntry(new Path("/classpath/lib/" +
+ (ScalaPlugin.plugin.shortScalaVer match {
+ case "2.8" => "2.9"
+ case "2.9" => "2.10"
+ case "2.10" => "2.8"
+ case _ =>
+ fail("Unsupported embedded scala library version " + ScalaPlugin.plugin.scalaVer +". Please update the test.")
+ ""
+ }) + ".x/my-scala-library.jar"), null, null)
+
+ setRawClasspathAndCheckMarkers(newRawClasspath, 0, 1)
+ }
+
+ /**
+ *
+ */
+
+ /**
+ * check that the error marker is kept even after a clean
+ */
+ @Test
+ def errorKeptAfterClean() {
+ setRawClasspathAndCheckMarkers(cleanRawClasspath, 0, 1)
+
+ project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor)
+ project.underlying.build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor)
+
+ checkMarkers(0, 1)
+ }
+
+ /**
+ * check the code is not compiled if the classpath is not right (no error reported in scala files)
+ */
+ @Test
+ def errorInClasspathStopBuild() {
+ project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor)
+ project.underlying.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new NullProgressMonitor)
+
+ // no error on the project itself
+ checkMarkers(0, 0)
+
+ // two excepted code errors
+ var markers= project.underlying.findMarkers("org.scala-ide.sdt.core.problem", false, IResource.DEPTH_INFINITE)
+ assertEquals("Unexpected number of scala problems in project", 2, markers.length)
+
+ // switch to an invalid classpath
+ setRawClasspathAndCheckMarkers(cleanRawClasspath, 0, 1)
+
+ project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor)
+ project.underlying.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new NullProgressMonitor)
+
+ // one error on the project
+ checkMarkers(0, 1)
+
+ // no additional code errors
+ markers= project.underlying.findMarkers("org.scala-ide.sdt.core.problem", false, IResource.DEPTH_INFINITE)
+ assertEquals("Unexpected number of scala problems in project", 1, markers.length)
+ }
+
+ /**
+ * Set the new classpath and check the number of errors and warnings attached to the project.
+ */
+ private def setRawClasspathAndCheckMarkers(newRawClasspath: Array[IClasspathEntry], expectedNbOfWarningMarker: Int, expectedNbOfErrorMarker: Int) {
+ project.javaProject.setRawClasspath(newRawClasspath, new NullProgressMonitor)
+ checkMarkers(expectedNbOfWarningMarker, expectedNbOfErrorMarker)
+ }
+
+ /**
+ * Check the number of errors and warnings attached to the project.
+ */
+ private def checkMarkers(expectedNbOfWarningMarker: Int, expectedNbOfErrorMarker: Int) {
+ val TIMEOUT= 5000
+
+ // check the classpathValid state
+ assertEquals("Unexpected classpath validity state", expectedNbOfErrorMarker == 0, project.isClasspathValid())
+
+ var nbOfWarningMarker= 0
+ var nbOfErrorMarker= 0
+
+ for (i <- 1 to (TIMEOUT / 200)) {
+ // count the markers on the project
+ nbOfWarningMarker= 0
+ nbOfErrorMarker= 0
+ for (marker <- project.underlying.findMarkers("org.scala-ide.sdt.core.problem", false, IResource.DEPTH_ZERO))
+ marker.getAttribute(IMarker.SEVERITY, 0) match {
+ case IMarker.SEVERITY_ERROR => nbOfErrorMarker+=1
+ case IMarker.SEVERITY_WARNING => nbOfWarningMarker+=1
+ case _ =>
+ }
+
+ if (nbOfWarningMarker == expectedNbOfWarningMarker && nbOfErrorMarker == expectedNbOfErrorMarker) {
+ // markers are fine, we're done
+ return
+ }
+
+ // wait a bit before trying again
+ Thread.sleep(200)
+ }
+
+ // after TIMEOUT, we didn't get the expected value
+ assertEquals("Unexpected nb of warning markers", expectedNbOfWarningMarker, nbOfWarningMarker)
+ assertEquals("Unexpected nb of error markers", expectedNbOfErrorMarker, nbOfErrorMarker)
+ }
+
+}
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>classpath</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.scala-ide.sdt.core.scalabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.scala-ide.sdt.core.scalanature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
@@ -0,0 +1,12 @@
+package test
+
+class Test {
+
+ var x: ArrayList= null
+
+ def foo() {
+ var s= "s"
+ s= 2
+ }
+
+}
@@ -6,16 +6,18 @@
package scala.tools.eclipse
import scala.collection.mutable.HashSet
+
import java.{ lang => jl, util => ju }
+
import org.eclipse.core.resources.{ IFile, IncrementalProjectBuilder, IProject, IResource, IResourceDelta, IResourceDeltaVisitor, IResourceVisitor }
import org.eclipse.core.runtime.{ IProgressMonitor, IPath, SubMonitor }
import org.eclipse.jdt.internal.core.JavaModelManager
import org.eclipse.jdt.internal.core.builder.{ JavaBuilder, NameEnvironment, State }
+
import scala.tools.eclipse.javaelements.JDTUtils
import scala.tools.eclipse.util.{ FileUtils, ReflectionUtils }
-import util.HasLogger
-class ScalaBuilder extends IncrementalProjectBuilder with HasLogger {
+class ScalaBuilder extends IncrementalProjectBuilder {
def plugin = ScalaPlugin.plugin
private val scalaJavaBuilder = new GeneralScalaJavaBuilder
@@ -34,6 +36,12 @@ class ScalaBuilder extends IncrementalProjectBuilder with HasLogger {
override def build(kind : Int, ignored : ju.Map[_, _], monitor : IProgressMonitor) : Array[IProject] = {
import IncrementalProjectBuilder._
import buildmanager.sbtintegration.EclipseSbtBuildManager
+
+ // check the classpath
+ if (!plugin.getScalaProject(getProject).isClasspathValid()) {
+ // bail out is the classpath in not valid
+ return new Array[IProject](0)
+ }
val project = plugin.getScalaProject(getProject)
@@ -66,17 +74,8 @@ class ScalaBuilder extends IncrementalProjectBuilder with HasLogger {
// Only for sbt which is able to track external dependencies properly
project.buildManager match {
case _: EclipseSbtBuildManager =>
-
- def hasChanges(prj: IProject): Boolean = {
- val delta = getDelta(prj)
- delta == null || delta.getKind != IResourceDelta.NO_CHANGE
- }
-
- if (project.externalDepends.exists(hasChanges)) {
- // reset presentation compilers if a dependency has been rebuilt
- logger.debug("Resetting presentation compiler for %s due to dependent project change".format(project.underlying.getName()))
- project.resetPresentationCompiler
-
+ if (project.externalDepends.exists(
+ x => { val delta = getDelta(x); delta == null || delta.getKind != IResourceDelta.NO_CHANGE})) {
// in theory need to be able to identify the exact dependencies
// but this is deeply rooted inside the sbt dependency tracking mechanism
// so we just tell it to have a look at all the files
Oops, something went wrong.

0 comments on commit 809cf3b

Please sign in to comment.