diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 58b0df1c9..e75c97832 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -570,11 +570,20 @@ object Defaults extends BuildCommon { def selectedFilter(args: Seq[String]): Seq[String => Boolean] = { - val filters = args map GlobFilter.apply - if (filters.isEmpty) + def matches(nfs: Seq[NameFilter], s: String) = nfs.exists(_.accept(s)) + + val (excludeArgs, includeArgs) = args.partition(_.startsWith("-")) + + val includeFilters = includeArgs map GlobFilter.apply + val excludeFilters = excludeArgs.map(_.substring(1)).map(GlobFilter.apply) + + if (includeFilters.isEmpty && excludeArgs.isEmpty) { Seq(const(true)) - else - filters.map { f => (s: String) => f accept s } + } else if (includeFilters.isEmpty) { + Seq({ (s: String) => !matches(excludeFilters, s) }) + } else { + includeFilters.map { f => (s: String) => (f.accept(s) && !matches(excludeFilters, s)) } + } } def detectTests: Initialize[Task[Seq[TestDefinition]]] = (loadedTestFrameworks, compile, streams) map { (frameworkMap, analysis, s) => Tests.discover(frameworkMap.values.toList, analysis, s.log)._1 @@ -896,7 +905,7 @@ object Defaults extends BuildCommon { selectTests ~ options } - def distinctParser(exs: Set[String], raw: Boolean): Parser[Seq[String]] = + private def distinctParser(exs: Set[String], raw: Boolean): Parser[Seq[String]] = { import DefaultParsers._ val base = token(Space) ~> token(NotSpace - "--" examples exs) diff --git a/main/src/test/scala/DefaultsTest.scala b/main/src/test/scala/DefaultsTest.scala new file mode 100644 index 000000000..c09a806c0 --- /dev/null +++ b/main/src/test/scala/DefaultsTest.scala @@ -0,0 +1,53 @@ +package sbt + +import java.io._ + +import org.specs2.mutable.Specification + +object DefaultsTest extends Specification { + private def assertFiltered(filter: List[String], expected: Map[String, Boolean]) = { + val actual = expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) + + actual must be equalTo (expected) + } + + "`selectedFilter`" should { + "return all tests for an empty list" in { + assertFiltered(List(), Map("Test1" -> true, "Test2" -> true)) + } + + "work correctly with exact matches" in { + assertFiltered(List("Test1", "foo"), Map("Test1" -> true, "Test2" -> false, "Foo" -> false)) + } + + "work correctly with glob" in { + assertFiltered(List("Test*"), Map("Test1" -> true, "Test2" -> true, "Foo" -> false)) + } + + "work correctly with excludes" in { + assertFiltered(List("Test*", "-Test2"), Map("Test1" -> true, "Test2" -> false, "Foo" -> false)) + } + + "work correctly without includes" in { + assertFiltered(List("-Test2"), Map("Test1" -> true, "Test2" -> false, "Foo" -> true)) + } + + "work correctly with excluded globs" in { + assertFiltered(List("Test*", "-F*"), Map("Test1" -> true, "Test2" -> true, "Foo" -> false)) + } + + "cope with multiple filters" in { + assertFiltered(List("T*1", "T*2", "-F*"), Map("Test1" -> true, "Test2" -> true, "Foo" -> false)) + } + + "cope with multiple exclusion filters, no includes" in { + assertFiltered(List("-A*", "-F*"), Map("Test1" -> true, "Test2" -> true, "AAA" -> false, "Foo" -> false)) + } + + "cope with multiple exclusion filters with includes" in { + assertFiltered(List("T*", "-T*1", "-T*2"), Map("Test1" -> false, "Test2" -> false, "Test3" -> true)) + } + } + +} + diff --git a/sbt/src/sbt-test/tests/test-exclude/project/Build.scala b/sbt/src/sbt-test/tests/test-exclude/project/Build.scala new file mode 100644 index 000000000..7f2f30f93 --- /dev/null +++ b/sbt/src/sbt-test/tests/test-exclude/project/Build.scala @@ -0,0 +1,10 @@ +import sbt._ +import Keys._ +import Defaults._ + +object B extends Build { + lazy val root = Project("root", file("."), settings = defaultSettings ++ Seq( + libraryDependencies += "org.scalatest" %% "scalatest" % "1.9.1" % "test", + parallelExecution in test := false + )) +} diff --git a/sbt/src/sbt-test/tests/test-exclude/src/test/scala/Test.scala b/sbt/src/sbt-test/tests/test-exclude/src/test/scala/Test.scala new file mode 100644 index 000000000..c7b0235e2 --- /dev/null +++ b/sbt/src/sbt-test/tests/test-exclude/src/test/scala/Test.scala @@ -0,0 +1,15 @@ +import java.io.File +import org.scalatest.FlatSpec +import org.scalatest.matchers.ShouldMatchers + +class Test1 extends FlatSpec with ShouldMatchers { + "a test" should "pass" in { + new File("target/Test1.run").createNewFile() + } +} + +class Test2 extends FlatSpec with ShouldMatchers { + "a test" should "pass" in { + new File("target/Test2.run").createNewFile() + } +} diff --git a/sbt/src/sbt-test/tests/test-exclude/test b/sbt/src/sbt-test/tests/test-exclude/test new file mode 100644 index 000000000..913c97312 --- /dev/null +++ b/sbt/src/sbt-test/tests/test-exclude/test @@ -0,0 +1,61 @@ +# Test1 & Test2 create files Test1.run & Test2.run respectively + +# no parameters +> test-only +$ exists target/Test1.run +$ exists target/Test2.run + +$ delete target/Test1.run +$ delete target/Test2.run + + +# with explicit match +> test-only Test1* +$ exists target/Test1.run +-$ exists target/Test2.run + +$ delete target/Test1.run + + +# with explicit match and exclusion +> test-only Test* -Test1 +-$ exists target/Test1.run +$ exists target/Test2.run + +$ delete target/Test2.run + + +# with explicit match and exclusion +> test-only Test* -Test2 +$ exists target/Test1.run +-$ exists target/Test2.run + +$ delete target/Test1.run + + +# with only exclusion +> test-only -Test2 +$ exists target/Test1.run +-$ exists target/Test2.run + +$ delete target/Test1.run + + +# with only exclusion +> test-only -Test1 +-$ exists target/Test1.run +$ exists target/Test2.run + +$ delete target/Test2.run + + +# with only glob exclusion +> test-only -Test* +-$ exists target/Test1.run +-$ exists target/Test2.run + + +# with only glob exclusion +> test-only -T*1 -T*2 +-$ exists target/Test1.run +-$ exists target/Test2.run