| /* |
| * Copyright 2001-2008 Artima, Inc. |
| * |
| * 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.scalatest.junit |
| |
| import org.junit.runner.{Description, RunWith} |
| import org.scalatest._ |
| import org.junit.runner.notification.RunNotifier |
| import tools.ConcurrentDistributor |
| import java.util.concurrent.{ExecutorService, Executors} |
| import scala.Some |
| |
| /* |
| I think that Stopper really should be a no-op, like it is, because the user has |
| no way to stop it. This is wierd, because it will call nested suites. So the tests |
| just pile up. Oh, I see, the darn information about which test it is is in the |
| stupid Description displayName. We probably need to add optional test name and |
| suite class name to Report, just to satisfy JUnit integration. |
| */ |
| /** |
| * A JUnit <code>Runner</code> that knows how to run any ScalaTest <code>Suite</code> |
| * (or <code>Spec</code>, which extends <code>Suite</code>). |
| * This enables you to provide a JUnit <code>RunWith</code> annotation on any |
| * ScalaTest <code>Suite</code>. Here's an example: |
| * |
| * <pre> |
| * import org.junit.runner.RunWith |
| * import org.scalatest.junit.JUnitRunner |
| * import org.scalatest.FunSuite |
| * |
| * @RunWith( c l a s s O f[ J U n i t R u n n e r ] ) |
| * class MySuite extends FunSuite { |
| * // ... |
| * } |
| * </pre> |
| * |
| * <p> |
| * This <code>RunWith</code> annotation will enable the <code>MySuite</code> class |
| * to be run by JUnit 4. |
| * </p> |
| * |
| * @author Bill Venners |
| * @author Daniel Watson |
| * @author Jon-Anders Teigen |
| * @author Colin Howe |
| */ |
| @RunWith(classOf[JUnitRunner]) |
| final class ParallelJUnitRunner(suiteClass: java.lang.Class[Suite]) extends org.junit.runner.Runner { |
| |
| private val canInstantiate = Suite.checkForPublicNoArgConstructor(suiteClass) |
| require(canInstantiate, "Must pass an org.scalatest.Suite with a public no-arg constructor") |
| |
| private val suiteToRun = suiteClass.newInstance |
| |
| /** |
| * Get a JUnit <code>Description</code> for this ScalaTest <code>Suite</code> of tests. |
| * |
| * return a <code>Description</code> of this suite of tests |
| */ |
| val getDescription = createDescription(suiteToRun) |
| |
| private def createDescription(suite: Suite): Description = { |
| val description = Description.createSuiteDescription(suite.getClass) |
| // If we don't add the testNames and nested suites in, we get |
| // Unrooted Tests show up in Eclipse |
| for (name <- suite.testNames) { |
| description.addChild(Description.createTestDescription(suite.getClass, name)) |
| } |
| for (nestedSuite <- suite.nestedSuites) { |
| description.addChild(createDescription(nestedSuite)) |
| } |
| description |
| } |
| |
| /** |
| * Run this <code>Suite</code> of tests, reporting results to the passed <code>RunNotifier</code>. |
| * This class's implementation of this method invokes <code>run</code> on an instance of the |
| * <code>suiteClass</code> <code>Class</code> passed to the primary constructor, passing |
| * in a <code>Reporter</code> that forwards to the <code>RunNotifier</code> passed to this |
| * method as <code>notifier</code>. |
| * |
| * @param notifier the JUnit <code>RunNotifier</code> to which to report the results of executing |
| * this suite of tests |
| */ |
| def run(notifier: RunNotifier) { |
| val reporter = new org.scalatest.junit.RunNotifierReporter(notifier) |
| var stopper = new Stopper {} |
| var filter = Filter() |
| var configMap = Map[String, Any]() |
| ParallelJUnitRunner.withExecutor { executor => |
| val distributor = new ConcurrentDistributor(reporter, stopper, filter, configMap, executor) |
| try { |
| suiteToRun.run(None, reporter, stopper, filter, configMap, Some(distributor), new Tracker) |
| } finally { |
| distributor.waitUntilDone() |
| } |
| } |
| } |
| |
| /** |
| * Returns the number of tests that are expected to run when this ScalaTest <code>Suite</code> |
| * is run. |
| * |
| * @return the expected number of tests that will run when this suite is run |
| */ |
| override def testCount() = suiteToRun.expectedTestCount(Filter()) |
| } |
| |
| object ParallelJUnitRunner { |
| |
| private var useCounter = 0 |
| private var executor:ExecutorService = _ |
| |
| // We share an executor across multiple concurrent test executions. |
| def withExecutor[T](fun: (ExecutorService)=>T):T= { |
| val value = this.synchronized { |
| useCounter+=1 |
| if( executor == null) { |
| var threads = Integer.getInteger("test.threads", Runtime.getRuntime.availableProcessors*2) |
| println("ParallelJUnitRunner using up to "+threads+" threads to execute parallel tests.") |
| executor = Executors.newFixedThreadPool(threads); |
| } |
| executor |
| } |
| try { |
| fun(value) |
| } finally { |
| val shutdown = this.synchronized { |
| useCounter-=1 |
| if( useCounter == 0) { |
| executor = null |
| true |
| } else { |
| false |
| } |
| } |
| if ( shutdown ) { |
| value.shutdown() |
| } |
| } |
| } |
| |
| } |