diff options
author | Jesse Luehrs <doy@tozt.net> | 2013-03-06 15:32:26 -0600 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2013-03-06 15:32:26 -0600 |
commit | 7efb2caf7d8832a7d3a9d2ac55862e43267a3eb2 (patch) | |
tree | 49fcb4d31bec67bcb67c1262abc25c5e5ecb1e51 /src/main/scala/org/perl8/test/TestMore.scala | |
parent | 66bcf3627a38ef58dabaf90b7e597569b91ea3e8 (diff) | |
download | scala-test-more-7efb2caf7d8832a7d3a9d2ac55862e43267a3eb2.tar.gz scala-test-more-7efb2caf7d8832a7d3a9d2ac55862e43267a3eb2.zip |
move the directory structure too
Diffstat (limited to 'src/main/scala/org/perl8/test/TestMore.scala')
-rw-r--r-- | src/main/scala/org/perl8/test/TestMore.scala | 519 |
1 files changed, 0 insertions, 519 deletions
diff --git a/src/main/scala/org/perl8/test/TestMore.scala b/src/main/scala/org/perl8/test/TestMore.scala deleted file mode 100644 index eb5210b..0000000 --- a/src/main/scala/org/perl8/test/TestMore.scala +++ /dev/null @@ -1,519 +0,0 @@ -package com.iinteractive.test - -import scala.util.matching.Regex - -import com.iinteractive.test.tap.TestBuilder - -/** This class is an implementation of the excellent - * [[https://metacpan.org/module/Test::More Test::More]] testing library for - * Perl. It provides a simple assertion-based testing API, which produces - * [[http://en.wikipedia.org/wiki/Test_Anything_Protocol TAP]], which can be - * parsed by any TAP consumer. This library includes several TAP-consuming - * harnesses to use with tests using this class, including one that supports - * testing via `sbt test`. - * - * ==Basics== - * - * The most basic test looks like this: - * - * <pre> - * class MyTest extends TestMore { - * ok(true) - * } - * </pre> - * - * This runs a test containing a single assertion. This will generate a TAP - * stream that looks like this: - * - * <pre> - * ok 1 - * 1..1 - * </pre> - * - * which can be parsed by one of the test harnesses provided by this library. - * - * ==Running tests== - * - * The simplest way to run tests is through sbt. You can register this - * framework with sbt by adding this line to your `build.sbt` file: - * - * <pre> - * testFrameworks += new TestFramework("com.iinteractive.test.sbt.Framework") - * </pre> - * - * Then, any classes in your test directory which extend `TestMore` will be - * automatically detected and run. - * - * ==Assertions== - * - * This class contains many more assertion methods than just `ok`. Here is a - * more extensive example (borrowed from Test::More's documentation): - * - * <pre> - * class MyTest extends TestMore { - * ok(got == expected, testName) - * - * is(got, expected, testName) - * isnt(got, expected, testName) - * - * diag("here's what went wrong") - * - * like(got, """expected""".r, testName) - * unlike(got, """expected""".r, testName) - * - * skip(howMany, why) { - * ok(foo(), testName) - * is(foo(42), 23, testName) - * } - * - * todo(why) { - * ok(foo(), testName) - * is(foo(42), 23, testName) - * } - * - * pass(testName) - * fail(testName) - * - * BAIL_OUT(why) - * } - * </pre> - * - * The difference between the simple `ok` method and the more specific - * methods like `is` and `like` is in how failures are reported. If you write - * this: - * - * <pre> - * ok(1 == 2) - * </pre> - * - * the output will look like this: - * - * <pre> - * not ok 1 - * # Failed test at MyTest.scala line 4. - * </pre> - * - * On the other hand, a more specific assertion such as: - * - * <pre> - * is(1, 2) - * </pre> - * - * will produce more useful output: - * - * <pre> - * not ok 1 - * # Failed test at MyTest.scala line 4. - * # got: 1 - * # expected: 2 - * </pre> - * - * In addition to assertions, there are also several methods which take a - * block of code to run, to modify the assertions contained in that block. - * - * The `todo` method runs tests which are expected to fail. If they do fail, - * the failure is reported to the test harness as a normal succeeding test, - * and nothing happens. If they succeed, they are still reported as a - * succeeding test, but a message is displayed to the user indicating that - * the todo indication can be removed. - * - * The `skip` method takes a block which should not be run at all. This is - * similar to `todo`, except that it is useful for tests which could cause - * problems if they were to actually run. Since the tests are never run, it's - * not possible to count how many tests there should be, so this must be - * specified as a parameter. - * - * The `subtest` method runs a block of assertions as though they were an - * entirely separate test, and then reports the result of that test as a - * single assertion in the test that called `subtest`. - * - * ==Test plans== - * - * Normally, you can run any number of assertions within your class body, and - * the framework will assume that if no exceptions were thrown, all of the - * assertions that were meant to be run were actually run. Sometimes, - * however, that may not be a safe assumption, especially with heavily - * callback-driven code. In this case, you can specify exactly how many tests - * you intend to run, and the number of tests actually run will be checked - * against this at the end. To declare this, give a number to the `TestMore` - * constructor: - * - * <pre> - * class MyTest extends TestMore(5) { - * ??? - * } - * </pre> - * - * In addition, if the entire test should be skipped, you can give a plan of - * `SkipAll()`: - * - * <pre> - * class MyTest extends TestMore(SkipAll()) { - * ??? - * } - * </pre> - * - * ==Extensions== - * - * These assertion methods are written with the intention of being - * composable. You can write your own test methods which call `is` or `ok` on - * more specific bits of data. The one issue here is that, as shown above, - * test failure messages refer to the file and line where the `is` or `ok` - * call was made. If you want this to instead point at the line where your - * assertion helper method was called, you can use the `hideTestMethod` - * method like this: - * - * <pre> - * trait MyTestHelpers { this: TestMore => - * def notok (cond: Boolean) = hideTestMethod { - * ok(!cond) - * } - * } - * </pre> - * - * This way, the test failure will be reported from the line where `notok` - * was called, not from the call to `ok` in the `notok` method. - */ -class TestMore (plan: Plan = NoPlan) extends Test with DelayedInit { - def delayedInit (body: => Unit) { - testBody = { terminalInUse => - todo = None - builder = new TestBuilder(plan, terminalInUse) - plan match { - case SkipAll(_) => () - case _ => body - } - } - } - - protected def runTests (terminalInUse: Boolean): Int = { - if (testBody == null) { - delayedInit { } - } - - testBody(terminalInUse) - builder.doneTesting - builder.exitCode - } - - /** Assert that a condition is true. - * - * @example `ok(response.isSuccess)` - */ - def ok (cond: Boolean): Boolean = - test(cond) - - /** Assert that a condition is true, and describe the assertion. - * - * @example `ok(response.isSuccess, "the response succeeded")` - */ - def ok (cond: Boolean, desc: String): Boolean = - testWithDesc(cond, desc) - - /** Assert that two objects are equal (using `==`). - * - * @example `is(response.status, 200)` - */ - def is[T] (got: T, expected: T): Boolean = - test(got == expected, isMessage(got, expected)) - - /** Assert that two objects are equal (using `==`), and describe the - * assertion. - * - * @example `is(response.status, 200, "we got a 200 OK response")` - */ - def is[T] (got: T, expected: T, desc: String): Boolean = - testWithDesc(got == expected, desc, isMessage(got, expected)) - - /** Assert that two objects are not equal (using `!=`). - * - * @example `isnt(response.body, "")` - */ - def isnt[T] (got: T, expected: T): Boolean = - test(got != expected, isntMessage(got)) - - /** Assert that two objects are not equal (using `!=`), and describe the - * assertion. - * - * @example `isnt(response.body, "", "we got a response body")` - */ - def isnt[T] (got: T, expected: T, desc: String): Boolean = - testWithDesc(got != expected, desc, isntMessage(got)) - - /** Assert that a string matches a regular expression. - * - * @example `like(response.header("Content-Type"), """text/x?html""".r)` - */ - def like (got: String, rx: Regex): Boolean = - test(rx.findFirstIn(got).nonEmpty, likeMessage(got, rx)) - - /** Assert that a string matches a regular expression, and describe the - * assertion. - * - * @example `like(response.header("Content-Type"), """text/x?html""".r, "we got an html content type")` - */ - def like (got: String, rx: Regex, desc: String): Boolean = - testWithDesc(rx.findFirstIn(got).nonEmpty, desc, likeMessage(got, rx)) - - /** Assert that a string doesn't match a regular expression. - * - * @example `unlike(response.header("Authorization"), """^Digest.*""".r)` - */ - def unlike (got: String, rx: Regex): Boolean = - test(rx.findFirstIn(got).isEmpty, unlikeMessage(got, rx)) - - /** Assert that a string doesn't match a regular expression. - * - * @example `unlike(response.header("Authorization"), """^Digest.*""".r, "we don't support digest authentication")` - */ - def unlike (got: String, rx: Regex, desc: String): Boolean = - testWithDesc(rx.findFirstIn(got).isEmpty, desc, unlikeMessage(got, rx)) - - /** An assertion that always succeeds. - * - * @example `pass()` - */ - def pass: Boolean = - ok(true) - - /** An assertion that always succeeds, with a reason. - * - * @example `pass("this line of code should be executed")` - */ - def pass (desc: String): Boolean = - ok(true, desc) - - /** An assertion that always fails. - * - * @example `fail()` - */ - def fail: Boolean = - ok(false) - - /** An assertion that always fails, with a reason. - * - * @example `fail("we should never get here")` - */ - def fail (desc: String): Boolean = - ok(false, desc) - - /** Output a comment to `Console.err`. This is intended to be visible to - * users even when running the test under a summarizing harness. - * - * @example `diag("Testing with Scala " + util.Properties.versionString)` - */ - def diag (message: String) { - builder.diag(message) - } - - /** Output a comment to `Console.out`. This is intended to only be visible - * when viewing the raw TAP stream. - * - * @example `note("Starting the response tests")` - */ - def note (message: String) { - builder.note(message) - } - - /** Halt execution of the entire test suite. - * - * @example `BAIL_OUT("can't connect to the database!")` - */ - def BAIL_OUT (desc: String) { - builder.bailOut(desc) - } - - /** Mark a block of tests as expected to fail. If the tests which run in the - * todo block fail, they will not be treated as test failures, and if they - * succeed, the user will be notified. - * - * @example `todo("waiting on fixes elsewhere") { ??? }` - */ - def todo (reason: String)(body: => Unit) { - val oldTodo = todo - try { - todo = Some(reason) - body - } - finally { - todo = oldTodo - } - } - - /** Mark a block of tests that should not be run at all. They are treated as - * always passing. - * - * @example `skip(3, "too dangerous to run for now") { ??? }` - */ - def skip (count: Int, reason: String)(body: => Unit) { - for (i <- 1 to count) { - builder.skip(reason) - } - } - - /** Declare a logical group of assertions, to be run as a single test. This - * is effectively an entirely separate test, which is run, and the result - * of that test is reported as a single assertion in the test that contains - * it. The subtest can specify its own plan in the same way that the - * overall test is allowed to. The name will be used as the description for - * the single assertion that the overall test sees. - * - * @example `subtest("response tests") { ??? }` - */ - def subtest ( - name: String, - plan: Plan = NoPlan - )(body: => Unit): Boolean = { - val oldBuilder = builder - val success = try { - builder = oldBuilder.cloneForSubtest(plan) - body - builder.doneTesting - } - finally { - builder = oldBuilder - } - ok(success, name) - } - - /** A helper method which should be used to wrap test utility methods. - * Normally, when tests fail, a message is printed giving the file and line - * number of the call to the test method. If you write your own test - * methods, they will typically use the existing methods to generate - * assertions, and so the file and line numbers will likely be much less - * useful. Wrapping the body of your method in this method will ensure that - * the file and line number that is reported is the line where your helper - * method is called instead. - * - * @example `def testFixtures = hideTestMethod { ??? }` - */ - def hideTestMethod[T] (body: => T): T = { - // this just adds a method call with a known name to the stack trace, so - // that we can detect it later - body - } - - private def isMessage[T] (got: T, expected: T): String = - " got: '" + got + "'\n" + - " expected: '" + expected + "'\n" - - private def isntMessage[T] (got: T): String = - " got: '" + got + "'\n" + - " expected: anything else\n" - - private def likeMessage (got: String, rx: Regex): String = - " '" + got + "'\n" + - " doesn't match '" + rx + "'\n" - - private def unlikeMessage (got: String, rx: Regex): String = - " '" + got + "'\n" + - " matches '" + rx + "'\n" - - private def testWithDesc ( - cond: Boolean, - desc: String - ): Boolean = { - todo match { - case Some(t) => builder.todo(t, cond, "- " + desc) - case None => builder.ok(cond, "- " + desc) - } - if (!cond) { - failed(Some(desc), None) - } - cond - } - - private def testWithDesc ( - cond: Boolean, - desc: String, - reason: => String - ): Boolean = { - todo match { - case Some(t) => builder.todo(t, cond, "- " + desc) - case None => builder.ok(cond, "- " + desc) - } - if (!cond) { - failed(Some(desc), Some(reason)) - } - cond - } - - private def test (cond: Boolean): Boolean = { - todo match { - case Some(t) => builder.todo(t, cond) - case None => builder.ok(cond) - } - if (!cond) { - failed(None, None) - } - cond - } - - private def test (cond: Boolean, reason: => String): Boolean = { - todo match { - case Some(t) => builder.todo(t, cond) - case None => builder.ok(cond) - } - if (!cond) { - failed(None, Some(reason)) - } - cond - } - - private def failed (desc: Option[String], reason: Option[String]) { - val stack = Thread.currentThread.getStackTrace.drop(1).filter { frame => - !ignoreFrame(frame) - } - val idx = stack.lastIndexWhere { frame => - frame.getClassName == "com.iinteractive.test.TestMore" && - frame.getMethodName == "hideTestMethod" - } - val caller = idx match { - case -1 => stack.headOption - // one level to jump out of hideTestMethod and one level to jump out of - // the method that called hideTestMethod - case i => stack.drop(i + 2).headOption - - } - val (file, line) = caller match { - case Some(frame) => (frame.getFileName, frame.getLineNumber) - case None => ("<unknown file>", "<unknown line>") - } - val message = " " + (todo match { - case Some(_) => "Failed (TODO) test" - case None => "Failed test" - }) + (desc match { - case Some(m) => " '" + m + "'\n " - case None => " " - }) - val trace = "at " + file + " line " + line + "." - val explanation = message + trace + reason.map("\n" + _).getOrElse("") - if (todo.isDefined) { - builder.note(explanation) - } - else { - builder.diag(explanation) - } - } - - protected def ignoreFrame (frame: StackTraceElement): Boolean = { - val className = frame.getClassName - val methodName = frame.getMethodName - - // ignore everything in this class, except the hideTestMethod call which we - // use as a stack trace marker - (className == "com.iinteractive.test.TestMore" && - methodName != "hideTestMethod") || - // when you call a method in a class when the method is defined in a - // trait, it calls a stub which calls the real definition in the trait. - // the trait is represented under the hood as a class with the same name - // as the trait, except with $class appended. this is a gross reliance on - // implementation details that could change at any moment, but i don't - // really see any better options. - """\$class$""".r.findFirstIn(className).nonEmpty - } - - private var todo: Option[String] = _ - private var builder: TestBuilder = _ - private var testBody: Boolean => Unit = _ -} |