From 4594bdd37cd7fa9746eac91c3139dff1704f8f3b Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sat, 25 Apr 2015 00:11:48 -0700 Subject: Unit tests! --- .../no_carrier/github/FancyIssue.scala | 28 +- .../no_carrier/github/util/package.scala | 7 +- src/test/scala/FancyIssueSpec.scala | 531 ++++++++++++++++++++- src/test/scala/test_implicits/package.scala | 9 +- 4 files changed, 554 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/main/scala/com/getbootstrap/no_carrier/github/FancyIssue.scala b/src/main/scala/com/getbootstrap/no_carrier/github/FancyIssue.scala index 8b6c6e4..ba6f692 100644 --- a/src/main/scala/com/getbootstrap/no_carrier/github/FancyIssue.scala +++ b/src/main/scala/com/getbootstrap/no_carrier/github/FancyIssue.scala @@ -7,17 +7,27 @@ import com.getbootstrap.no_carrier.github.util._ import InstantOrdering._ class FancyIssue(val issue: Issue, val label: String, val timeout: Duration)(implicit clock: Clock) { - lazy val lastLabelledAt: Instant = issue.lastLabelledWithAt(label).get + lazy val lastLabelledAt: Option[Instant] = issue.lastLabelledWithAt(label) lazy val lastCommentedOnAt: Option[Instant] = issue.commentsIterable.lastOption.map{ _.smart.createdAt.toInstant } lazy val lastClosedAt: Option[Instant] = issue.smart.lastClosure.map{ _.smart.createdAt.toInstant } - lazy val hasSubsequentComment: Boolean = lastCommentedOnAt match { - case None => false - case Some(commentedAt) => lastLabelledAt < commentedAt + lazy val lastReopenedAt: Option[Instant] = issue.smart.lastReopening.map{ _.smart.createdAt.toInstant } + lazy val hasSubsequentComment: Boolean = (lastLabelledAt, lastCommentedOnAt) match { + case (Some(labeledAt), Some(commentedAt)) => labeledAt < commentedAt + case _ => false } - lazy val wasClosedAfterLabelling: Boolean = lastClosedAt match { - case None => false - case Some(closedAt) => lastLabelledAt < closedAt + lazy val wasClosedAfterLabelling: Boolean = (lastLabelledAt, lastClosedAt) match { + case (Some(labeledAt), Some(closedAt)) => labeledAt < closedAt + case _ => false + } + lazy val wasReopenedAfterLabelling: Boolean = (lastLabelledAt, lastReopenedAt) match { + case (Some(labeledAt), Some(reopenedAt)) => labeledAt < reopenedAt + case _ => false + } + lazy val opennessChangedAfterLabelling: Boolean = wasClosedAfterLabelling || wasReopenedAfterLabelling + lazy val isPastDeadline: Boolean = lastLabelledAt.exists{ _ isBeyondTimeout timeout } + lazy val opNeverDelivered: Boolean = { + val res = issue.smart.isOpen && issue.labels.smart.contains(label) && isPastDeadline && !opennessChangedAfterLabelling && !hasSubsequentComment + print(".") + res } - lazy val isPastDeadline: Boolean = lastLabelledAt isBeyondTimeout timeout - lazy val opNeverDelivered: Boolean = isPastDeadline && !wasClosedAfterLabelling && !hasSubsequentComment } diff --git a/src/main/scala/com/getbootstrap/no_carrier/github/util/package.scala b/src/main/scala/com/getbootstrap/no_carrier/github/util/package.scala index ff2233c..99efe1a 100644 --- a/src/main/scala/com/getbootstrap/no_carrier/github/util/package.scala +++ b/src/main/scala/com/getbootstrap/no_carrier/github/util/package.scala @@ -33,8 +33,10 @@ package object util { } implicit class RichSmartIssue(issue: SmartIssue) { - def lastClosure: Option[IssueEvent] = { - Try{ Some(issue.latestEvent(IssueEvent.CLOSED)) }.recover{ + def lastClosure: Option[IssueEvent] = latestEventOption(IssueEvent.CLOSED) + def lastReopening: Option[IssueEvent] = latestEventOption(IssueEvent.REOPENED) + def latestEventOption(eventType: String): Option[IssueEvent] = { + Try{ Some(issue.latestEvent(eventType)) }.recover{ case _:IllegalStateException => None }.get } @@ -68,6 +70,7 @@ package object util { } implicit class RichIssueLabels(labels: IssueLabels) { + def smart: IssueLabels.Smart = new IssueLabels.Smart(labels) def add(label: String) { val singleton = new java.util.LinkedList[String]() singleton.add(label) diff --git a/src/test/scala/FancyIssueSpec.scala b/src/test/scala/FancyIssueSpec.scala index 956c987..67c6f6c 100644 --- a/src/test/scala/FancyIssueSpec.scala +++ b/src/test/scala/FancyIssueSpec.scala @@ -1,6 +1,6 @@ -import java.time.{Clock, Instant} -import scala.collection.JavaConverters._ +import java.time.{Clock,Duration} import org.specs2.mutable._ +import com.jcabi.github.Issue import com.jcabi.github.mock.MkGithub import com.getbootstrap.no_carrier.github.util._ import test_implicits._ @@ -10,13 +10,27 @@ class FancyIssueSpec extends Specification { val github = new MkGithub("mock_gh") val repo = github.repos.create("foobar") val label = "waiting-for-OP" - val twoSecsInMs = 2000 + val twoSecsInMs = Duration.ofSeconds(2).toMillis + val tinyDuration = Duration.ofSeconds(2) + val tinyDelayMs = tinyDuration.toMillis + val mediumTimeout = Duration.ofSeconds(4) + val longDuration = Duration.ofSeconds(8) + val longDelayMs = longDuration.toMillis + val twoDays = Duration.ofDays(2) def newIssue() = repo.issues.create("Title here", "Description here") + def longDelay() { + Thread.sleep(longDelayMs) + } def delay() { Thread.sleep(twoSecsInMs) } + def tinyDelay() { + Thread.sleep(tinyDelayMs) + } "lastCommentedOnAt" should { + implicit val timeout = twoDays + "give the only value when there is exactly one comment" in { val issue = newIssue() val comment = issue.comments.post("First comment").smart @@ -34,6 +48,8 @@ class FancyIssueSpec extends Specification { } "lastLabelledAt" should { + implicit val timeout = twoDays + "give the maximum when there have been several labellings" in { val issue = newIssue() issue.labels.add(label) @@ -45,12 +61,14 @@ class FancyIssueSpec extends Specification { val before = clock.instant() issue.labels.add(label) val after = clock.instant() - issue.fancy.lastLabelledAt must beGreaterThanOrEqualTo(before.truncatedToSecs) - issue.fancy.lastLabelledAt must beLessThanOrEqualTo(after.truncatedToSecs) + issue.fancy.lastLabelledAt must beSome(greaterThanOrEqualTo(before.truncatedToSecs)) + issue.fancy.lastLabelledAt must beSome(lessThanOrEqualTo(after.truncatedToSecs)) } } "lastClosedAt" should { + implicit val timeout = twoDays + "be None when the issue is open" in { val issue = newIssue() issue.fancy.lastClosedAt must beNone @@ -82,6 +100,8 @@ class FancyIssueSpec extends Specification { } "wasClosedAfterLabelling" should { + implicit val timeout = twoDays + "be false when the issue has never been closed" in { val issue = newIssue() issue.fancy.wasClosedAfterLabelling must beFalse @@ -104,9 +124,504 @@ class FancyIssueSpec extends Specification { issue.smart.open() issue.fancy.wasClosedAfterLabelling must beTrue } - "be correct when the issue has been labelled, closed, and reopened multiple times" in { - val issue = newIssue() - // FIXME + } + + def addLabel(issue: Issue) { + issue.labels.add(label) + tinyDelay() + } + def unlabel(issue: Issue) { + issue.labels.remove(label) + tinyDelay() + } + def close(issue: Issue) { + issue.smart.close() + tinyDelay() + } + def reopen(issue: Issue) { + issue.smart.open() + tinyDelay() + } + def commentOn(issue: Issue) { + issue.uniqueComment() + tinyDelay() + } + + "opNeverDelivered" should { + implicit val timeout = mediumTimeout + + "work correctly in a fairly exhaustive example" in { + var issue = newIssue() + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + close(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + issue.smart.open() + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + close(issue) + issue.fancy.opNeverDelivered must beFalse + issue.smart.open() + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + issue.smart.open() + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + // same as previous but without any delay + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + // same as previous but without any delay + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + // same as previous but without any delay + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + close(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + close(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + close(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + close(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + issue.labels.add(label) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + // same as previous but without any delay + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + + issue = newIssue() + close(issue) + issue.fancy.opNeverDelivered must beFalse + reopen(issue) + issue.fancy.opNeverDelivered must beFalse + addLabel(issue) + issue.fancy.opNeverDelivered must beFalse + longDelay() + issue.fancy.opNeverDelivered must beTrue + unlabel(issue) + issue.fancy.opNeverDelivered must beFalse + commentOn(issue) + issue.fancy.opNeverDelivered must beFalse + + ok } } } diff --git a/src/test/scala/test_implicits/package.scala b/src/test/scala/test_implicits/package.scala index 368c3cd..18ec42f 100644 --- a/src/test/scala/test_implicits/package.scala +++ b/src/test/scala/test_implicits/package.scala @@ -2,14 +2,19 @@ import java.time.{Instant, Duration, Clock} import java.time.temporal.ChronoUnit import com.jcabi.github.Issue import com.getbootstrap.no_carrier.github.FancyIssue +import com.getbootstrap.no_carrier.github.util._ package object test_implicits { object RicherIssue { private val label = "waiting-for-OP" - private val timeout = Duration.ofDays(2) + private var count = 1 } implicit class RicherIssue(issue: Issue) { - def fancy(implicit clock: Clock): FancyIssue = new FancyIssue(issue = issue, label = RicherIssue.label, timeout = RicherIssue.timeout) + def fancy(implicit clock: Clock, timeout: Duration): FancyIssue = new FancyIssue(issue = issue, label = RicherIssue.label, timeout = timeout) + def uniqueComment() { + issue.comments.post(s"Comment number ${RicherIssue.count}.") + RicherIssue.count += 1 + } } implicit class RicherInstant(instant: Instant) { -- cgit v1.2.3