Bootstrapping the gs-test framework.

This commit is contained in:
Pat Garrity 2024-08-21 15:14:27 -05:00
commit d9192843da
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
16 changed files with 907 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
target/
project/target/
project/project/
modules/core/target/
.version
.scala-build/

17
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,17 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: fix-byte-order-marker
- id: mixed-line-ending
args: ['--fix=lf']
description: Enforces using only 'LF' line endings.
- id: trailing-whitespace
- id: check-yaml
- repo: https://git.garrity.co/garrity-software/gs-pre-commit-scala
rev: v1.0.1
hooks:
- id: scalafmt

72
.scalafmt.conf Normal file
View file

@ -0,0 +1,72 @@
// See: https://github.com/scalameta/scalafmt/tags for the latest tags.
version = 3.8.1
runner.dialect = scala3
maxColumn = 80
rewrite {
rules = [RedundantBraces, RedundantParens, Imports, SortModifiers]
imports.expand = true
imports.sort = scalastyle
redundantBraces.ifElseExpressions = true
redundantBraces.stringInterpolation = true
}
indent {
main = 2
callSite = 2
defnSite = 2
extendSite = 4
withSiteRelativeToExtends = 2
commaSiteRelativeToExtends = 2
}
align {
preset = more
openParenCallSite = false
openParenDefnSite = false
}
newlines {
implicitParamListModifierForce = [before,after]
topLevelStatementBlankLines = [
{
blanks = 1
}
]
afterCurlyLambdaParams = squash
}
danglingParentheses {
defnSite = true
callSite = true
ctrlSite = true
exclude = []
}
verticalMultiline {
atDefnSite = true
arityThreshold = 2
newlineAfterOpenParen = true
}
comments {
wrap = standalone
}
docstrings {
style = "SpaceAsterisk"
oneline = unfold
wrap = yes
forceBlankLineBefore = true
}
project {
excludePaths = [
"glob:**target/**",
"glob:**.metals/**",
"glob:**.bloop/**",
"glob:**.bsp/**",
"glob:**metals.sbt",
"glob:**.git/**"
]
}

9
LICENSE Normal file
View file

@ -0,0 +1,9 @@
MIT License
Copyright Patrick Garrity
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

31
README.md Normal file
View file

@ -0,0 +1,31 @@
# gs-test
[GS Open Source](https://garrity.co/oss.html) |
[License (MIT)](./LICENSE)
Test framework for Scala 3. Based on
[Cats Effect](https://typelevel.org/cats-effect/) and
[FS2](https://fs2.io/#/).
- [Usage](#usage)
- [Dependency](#dependency)
- [Donate](#donate)
## Usage
### Dependency
This artifact is available in the Garrity Software Maven repository.
```scala
externalResolvers +=
"Garrity Software Releases" at "https://maven.garrity.co/gs"
val GsLog: ModuleID =
"gs" %% "gs-test-core-v0" % "$VERSION"
```
## Donate
Enjoy this project or want to help me achieve my [goals](https://garrity.co)?
Consider [Donating to Pat on Ko-fi](https://ko-fi.com/gspfm).

63
build.sbt Normal file
View file

@ -0,0 +1,63 @@
val scala3: String = "3.4.2"
ThisBuild / scalaVersion := scala3
ThisBuild / versionScheme := Some("semver-spec")
ThisBuild / gsProjectName := "gs-test"
ThisBuild / externalResolvers := Seq(
"Garrity Software Mirror" at "https://maven.garrity.co/releases",
"Garrity Software Releases" at "https://maven.garrity.co/gs"
)
val noPublishSettings = Seq(
publish := {}
)
val sharedSettings = Seq(
scalaVersion := scala3,
version := semVerSelected.value,
coverageFailOnMinimum := true
/* coverageMinimumStmtTotal := 100, coverageMinimumBranchTotal := 100 */
)
val Deps = new {
val Cats = new {
val Core: ModuleID = "org.typelevel" %% "cats-core" % "2.12.0"
val Effect: ModuleID = "org.typelevel" %% "cats-effect" % "3.5.4"
}
val Fs2 = new {
val Core: ModuleID = "co.fs2" %% "fs2-core" % "3.10.2"
}
val Gs = new {
val Uuid: ModuleID = "gs" %% "gs-uuid-v0" % "0.3.0"
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.2.0"
}
val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.0.1"
}
lazy val testSettings = Seq(
libraryDependencies ++= Seq(
Deps.MUnit % Test,
Deps.Gs.Datagen % Test
)
)
lazy val `gs-test` = project
.in(file("."))
.aggregate(core)
.settings(noPublishSettings)
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
lazy val core = project
.in(file("modules/core"))
.settings(sharedSettings)
.settings(testSettings)
.settings(name := s"${gsProjectName.value}-core-v${semVerMajor.value}")
.settings(libraryDependencies ++= Seq(
Deps.Cats.Core,
Deps.Cats.Effect,
Deps.Fs2.Core
))

View file

@ -0,0 +1,15 @@
package gs.test.v0
sealed trait GsTestError
object GsTestError:
sealed trait TestDefinitionError extends GsTestError
object TestDefinitionError:
case class InvalidIterations(candidate: Int) extends TestDefinitionError
end TestDefinitionError
end GsTestError

View file

@ -0,0 +1,38 @@
package gs.test.v0
import cats.Show
/**
* Opaque type representing some _permanent identifier_. These are
* user-assigned strings that are expected to _not change over time_ for some
* test. This allows tests to be deterministically tracked. The only constraint
* for a permanent identifier is that it must not be blank.
*
* ## Uniqueness
*
* Permanent identifiers are expected to be unique within the scope of a
* [[TestSuite]]. This means that two groups within the same suite _may not_
* contain tests that share a permanent identifier.
*/
opaque type PermanentId = String
object PermanentId:
/**
* Instantiate a new [[PermanentId]].
*
* @param candidate The candidate string.
* @return The new [[PermanentId]] instance.
* @throws IllegalArgumentException If the candidate string is blank.
*/
def apply(candidate: String): PermanentId =
if candidate.isBlank() then
throw new IllegalArgumentException("Permanent Identifiers must be non-blank.")
else
candidate
given CanEqual[PermanentId, PermanentId] = CanEqual.derived
given Show[PermanentId] = pid => pid
end PermanentId

View file

@ -0,0 +1,124 @@
package gs.test.v0
import cats.data.EitherT
import cats.Show
import gs.test.v0.GsTestError.TestDefinitionError.InvalidIterations
/**
* Each instance of this class indicates the _definition_ of some test.
*
* @param name The display name of the test. Not considered to be unique.
* @param permanentId The [[PermanentId]] for this test.
* @param tags The set of [[Test.Tag]] applicable to this test.
* @param iterations The number of iterations of this test to run.
* @param f The effect that the test evaluates.
*/
final class TestDefinition[F[_]](
val name: TestDefinition.Name,
val permanentId: PermanentId,
val documentation: Option[String],
val tags: List[TestDefinition.Tag],
val markers: List[TestDefinition.Marker],
val iterations: TestDefinition.Iterations,
val unitOfWork: EitherT[F, TestFailure, Unit]
)
object TestDefinition:
/**
* Opaque type representing names that may be assigned to [[Test]].
*/
opaque type Name = String
object Name:
/**
* Instantiate a new [[Test.Name]]. This name is not unique, has no
* constraints, and only exists for display purposes.
*
* @param name The candidate string.
* @return The new [[Test.Name]] instance.
*/
def apply(name: String): Name = name
given CanEqual[Name, Name] = CanEqual.derived
given Show[Name] = name => name
end Name
/**
* Opaque type representing tags that may be assigned to a [[Test]].
*/
opaque type Tag = String
object Tag:
/**
* Instantiate a new [[Test.Tag]].
*
* @param tag The candidate string.
* @return The new [[Test.Tag]] instance.
*/
def apply(tag: String): Tag = tag
given CanEqual[Tag, Tag] = CanEqual.derived
given Show[Tag] = tag => tag
end Tag
/**
* Enumeration for _Markers_, special tokens which "mark" a test to change
* execution functionality.
*
* The basic case for this enumeration is allowing tests to be ignored.
*
* @param name The formal serialized name of the marker.
*/
sealed abstract class Marker(val name: String)
object Marker:
given CanEqual[Marker, Marker] = CanEqual.derived
/**
* If this [[Test.Marker]] is present on a test, the test will be ignored.
*/
case object Ignored extends Marker("ignored")
end Marker
/**
* Opaque type that represents the number of iterations a test should run.
* This value must be at least `1` (the default). To ignore a test, use the
* [[Test.Marker.Ignored]] marker.
*/
opaque type Iterations = Int
object Iterations:
def One: Iterations = 1
/**
* Validate and instantiate a new [[Iterations]] instance.
*
* @param candidate The candidate value. Must be 1 or greater.
* @return The new [[Iterations]], or an error if an invalid input is given.
*/
def validate(candidate: Int): Either[GsTestError, Iterations] =
if candidate < 1 then
Left(InvalidIterations(candidate))
else
Right(candidate)
given CanEqual[Iterations, Iterations] = CanEqual.derived
given Show[Iterations] = iters => iters.toString()
extension (iters: Iterations)
def toInt(): Int = iters
end Iterations
end TestDefinition

View file

@ -0,0 +1,42 @@
package gs.test.v0
/**
* Base trait for all failures recognized by gs-test.
*/
sealed trait TestFailure
object TestFailure:
/**
* Returned when assertions in this library fail. Assertions understand how to
* populate these values.
*
* @param assertionName The name of the assertion.
* @param inputs The names and calculated types of each input to the assertion.
* @param message The message produced by the assertion.
*/
case class AssertionFailed(
assertionName: String,
inputs: Map[String, String],
message: String
) extends TestFailure
/**
* Return when a test explicitly calls `fail("...")` or some variant thereof.
*
* @param message The failure message provided by the test author.
*/
case class TestRequestedFailure(
message: String
) extends TestFailure
/**
* Used when the test fails due to an exception.
*
* @param cause The underlying cause of failure.
*/
case class ExceptionThrown(
cause: Throwable
) extends TestFailure
end TestFailure

View file

@ -0,0 +1,341 @@
package gs.test.v0
import cats.syntax.all.*
import cats.effect.Async
import scala.collection.mutable.ListBuffer
import gs.test.v0.TestDefinition.Tag
import gs.test.v0.TestDefinition.Marker
import gs.test.v0.TestDefinition.Iterations
import cats.data.EitherT
import java.util.concurrent.ConcurrentHashMap
import scala.jdk.CollectionConverters.*
/**
* Base class for defining groups of related tests. Users should extend this
* class to define their tests.
*/
abstract class TestGroup[F[_]: Async]:
def name: String
def tags: List[Tag] = List.empty
def markers: List[Marker] = List.empty
def documentation: Option[String] = None
private var beforeGroupValue: Option[F[Unit]] = None
private var afterGroupValue: Option[F[Unit]] = None
private var beforeEachTestValue: Option[F[Unit]] = None
private var afterEachTestValue: Option[F[Unit]] = None
private val registry: TestGroup.Registry[F] = new TestGroup.Registry[F]
def toGroupDefinition(): TestGroupDefinition[F] =
new TestGroupDefinition[F](
name = TestGroupDefinition.Name(name),
documentation = documentation,
testTags = tags,
testMarkers = markers,
beforeGroup = beforeGroupValue,
afterGroup = afterGroupValue,
beforeEachTest = beforeEachTestValue,
afterEachTest = afterEachTestValue,
tests = registry.toList()
)
protected def beforeGroup(f: => F[Unit]): Unit =
beforeGroupValue = Some(f)
()
protected def afterGroup(f: => F[Unit]): Unit =
afterGroupValue = Some(f)
()
protected def beforeEachTest(f: => F[Unit]): Unit =
beforeEachTestValue = Some(f)
()
protected def afterEachTest(f: => F[Unit]): Unit =
afterEachTestValue = Some(f)
()
/**
* Define a new test.
*
* ## Required Information
*
* All tests require 3 things, at minimum:
*
* - [[PermanentId]]
* - Display Name
* - Unit of Work (the code to execute)
*
* The [[PermanentId]] must be unique within the [[TestSuite]].
*
* ## Default Values
*
* Tests iterate 1 time by default. Tags and Markers are inherited from the
* parent group. If this group contains tag "foo", any test within this group
* will also get tag "foo".
*
* @param permanentId The [[PermanentId]] for this test.
* @param name The display name for this test.
* @return A builder, to help complete test definition.
*/
protected def test(
permanentId: PermanentId,
name: String
): TestGroup.TestBuilder[F] =
new TestGroup.TestBuilder[F](
registry = registry,
name = TestDefinition.Name(name),
permanentId = permanentId,
tags = ListBuffer(tags*),
markers = ListBuffer(markers*),
)
object TestGroup:
/**
* Specialization of [[TestGroup]] for `cats.effect.IO`, the typical use case.
*/
abstract class IO extends TestGroup[cats.effect.IO]
/**
* Builder to assist with defining tests.
*
* @param registry Registry instance internal to a [[TestGroup]] for recording completed definitions.
* @param name The name of the test.
* @param permanentId The [[PermanentId]] of the test.
* @param tags List of [[TestDefinition.Tag]] applicable to this test.
* @param markers List of [[TestDefinition.Marker]] applicable to this test.
* @param documentation The documentation for this test.
* @param iterations Number of iterations to run this test.
*/
protected final class TestBuilder[F[_]: Async](
val registry: Registry[F],
val name: TestDefinition.Name,
val permanentId: PermanentId,
private val tags: ListBuffer[Tag],
private val markers: ListBuffer[Marker],
private var documentation: Option[String] = None,
private var iterations: TestDefinition.Iterations = Iterations.One,
):
/**
* Supply documentation for this test.
*
* @param docs The documentation for this test.
* @return This builder.
*/
def document(docs: String): TestBuilder[F] =
documentation = Some(docs)
this
/**
* Add additional [[Test.Tag]] to this test definition.
*
* @param additionalTags The list of new tags.
* @return This builder.
*/
def tagged(additionalTags: Tag*): TestBuilder[F] =
val _ = tags.addAll(additionalTags)
this
/**
* Add the [[TestDefinition.Marker.Ignored]] marker to this test definition.
*
* @return This builder.
*/
def ignored(): TestBuilder[F] =
val _ = markers.addOne(Marker.Ignored)
this
/**
* Add one or more [[TestDefinition.Marker]] to this test definition.
*
* @param additionalMarkers The list of markers to add.
* @return This builder.
*/
def marked(additionalMarkers: Marker*): TestBuilder[F] =
val _ = markers.addAll(additionalMarkers)
this
/**
* Set the number of times this test should iterate.
*
* @param iters The number of iterations.
* @return This builder.
*/
def iterate(iters: Iterations): TestBuilder[F] =
iterations = iters
this
/**
* Provide an input supplier for this test. Note that each iteration of the
* test results in the input function being evaluated.
*
* @param f The input function.
* @return Builder that supports input.
*/
def input[Input](f: F[Input]): InputTestBuilder[F, Input] =
new InputTestBuilder[F, Input](
registry = registry,
name = name,
permanentId = permanentId,
inputFunction = f,
tags = tags,
markers = markers,
iterations = iterations
)
/**
* Finalize and register this test with a pure unit of work.
*
* @param unitOfWork The function this test will execute.
*/
def pure(unitOfWork: => Either[TestFailure, Unit]): Unit =
apply(EitherT.fromEither[F](unitOfWork))
/**
* Finalize and register this test with an effectful unit of work.
*
* @param unitOfWork The function this test will execute.
*/
def effectful(unitOfWork: => F[Either[TestFailure, Unit]]): Unit =
apply(EitherT(unitOfWork))
/**
* Finalize and register this test with an effectful unit of work.
*
* @param unitOfWork The function this test will execute.
*/
def apply(unitOfWork: => EitherT[F, TestFailure, Unit]): Unit =
registry.register(new TestDefinition[F](
name = name,
permanentId = permanentId,
documentation = documentation,
tags = tags.distinct.toList,
markers = markers.distinct.toList,
iterations = iterations,
unitOfWork = unitOfWork
))
/**
* Builder to assist with defining tests. This builder is for tests which
* accept input via some producing function.
*
* @param registry Registry instance internal to a [[TestGroup]] for recording completed definitions.
* @param name The name of the test.
* @param permanentId The [[PermanentId]] of the test.
* @param inputFunction The function that provides input to this test.
* @param tags List of [[TestDefinition.Tag]] applicable to this test.
* @param markers List of [[TestDefinition.Marker]] applicable to this test.
* @param documentation The documentation for this test.
* @param iterations Number of iterations to run this test.
*/
protected final class InputTestBuilder[F[_]: Async, Input](
val registry: Registry[F],
val name: TestDefinition.Name,
val permanentId: PermanentId,
val inputFunction: F[Input],
private val tags: ListBuffer[Tag],
private val markers: ListBuffer[Marker],
private var documentation: Option[String] = None,
private var iterations: TestDefinition.Iterations = Iterations.One,
):
/**
* Supply documentation for this test.
*
* @param docs The documentation for this test.
* @return This builder.
*/
def document(docs: String): InputTestBuilder[F, Input] =
documentation = Some(docs)
this
/**
* Add additional [[Test.Tag]] to this test definition.
*
* @param additionalTags The list of new tags.
* @return This builder.
*/
def tagged(additionalTags: Tag*): InputTestBuilder[F, Input] =
val _ = tags.addAll(additionalTags)
this
/**
* Add the [[TestDefinition.Marker.Ignored]] marker to this test definition.
*
* @return This builder.
*/
def ignored(): InputTestBuilder[F, Input] =
val _ = markers.addOne(Marker.Ignored)
this
/**
* Add one or more [[TestDefinition.Marker]] to this test definition.
*
* @param additionalMarkers The list of markers to add.
* @return This builder.
*/
def marked(additionalMarkers: Marker*): InputTestBuilder[F, Input] =
val _ = markers.addAll(additionalMarkers)
this
/**
* Set the number of times this test should iterate.
*
* @param iters The number of iterations.
* @return This builder.
*/
def iterate(iters: Iterations): InputTestBuilder[F, Input] =
iterations = iters
this
/**
* Finalize and register this test with a pure unit of work.
*
* @param unitOfWork The function this test will execute.
*/
def pure(unitOfWork: Input => Either[TestFailure, Unit]): Unit =
apply(input => EitherT(Async[F].delay(unitOfWork(input))))
/**
* Finalize and register this test with an effectful unit of work.
*
* @param unitOfWork The function this test will execute.
*/
def effectful(unitOfWork: Input => F[Either[TestFailure, Unit]]): Unit =
apply(input => EitherT(unitOfWork(input)))
/**
* Finalize and register this test with an effectful unit of work.
*
* @param unitOfWork The function this test will execute.
*/
def apply(unitOfWork: Input => EitherT[F, TestFailure, Unit]): Unit =
registry.register(new TestDefinition[F](
name = name,
permanentId = permanentId,
documentation = documentation,
tags = tags.distinct.toList,
markers = markers.distinct.toList,
iterations = iterations,
unitOfWork = EitherT.right(inputFunction).flatMap(unitOfWork)
))
protected final class Registry[F[_]]:
val mapping: ConcurrentHashMap[PermanentId, TestDefinition[F]] =
new ConcurrentHashMap[PermanentId, TestDefinition[F]]
def register(test: TestDefinition[F]): Unit =
if mapping.contains(test.permanentId) then
throw new IllegalArgumentException(
s"Attempted to register test with duplicate Permanent ID '${test.permanentId.show}'."
)
else
mapping.put(test.permanentId, test)
def toList(): List[TestDefinition[F]] = mapping.values().asScala.toList
end Registry
end TestGroup

View file

@ -0,0 +1,51 @@
package gs.test.v0
import cats.Show
/**
* Each group is comprised of a list of [[Test]]. This list may be empty.
*
* Groups are essentially metadata for tests for viewing/organization purposes.
*
* @param name The group name. Not considered to be unique.
* @param documentation Arbitrary documentation for this group of tests.
* @param testTags Set of tags applied to all [[Test]] within the group.
* @param tests The list of tests in this group.
*/
final class TestGroupDefinition[F[_]](
val name: TestGroupDefinition.Name,
val documentation: Option[String],
val testTags: List[TestDefinition.Tag],
val testMarkers: List[TestDefinition.Marker],
val beforeGroup: Option[F[Unit]],
val afterGroup: Option[F[Unit]],
val beforeEachTest: Option[F[Unit]],
val afterEachTest: Option[F[Unit]],
val tests: List[TestDefinition[F]]
)
object TestGroupDefinition:
/**
* Opaque type representing names that may be assigned to [[TestGroup]].
*/
opaque type Name = String
object Name:
/**
* Instantiate a new [[TestGroup.Name]]. This name is not unique, has no
* constraints, and only exists for display purposes.
*
* @param name The candidate string.
* @return The new [[TestGroup.Name]] instance.
*/
def apply(name: String): Name = name
given CanEqual[Name, Name] = CanEqual.derived
given Show[Name] = name => name
end Name
end TestGroupDefinition

View file

@ -0,0 +1,19 @@
package gs.test.v0
/**
* The Test Suite is the primary unit of organization within `gs-test` -- each
* execution _typically_ runs a single test suite. For example, the unit tests
* for some project would likely comprise of a single suite.
*
* Within each suite is a list of [[TestGroup]], arbitrary ways to organize
* individual [[Test]] definitions.
*
* @param name The name of this test suite.
* @param documentation Arbitrary documentation for this suite of tests.
* @param groups List of [[TestGroup]] owned by this suite.
*/
case class TestSuite[F[_]](
name: String,
documentation: Option[String],
groups: List[TestGroupDefinition[F]]
)

View file

@ -0,0 +1,45 @@
package gs.test.v0
import munit.*
class GroupImplementationTests extends FunSuite:
import GroupImplementationTests.*
test("should support a group with a simple, pure, test") {
val g1 = new G1
val group = g1.toGroupDefinition()
assertEquals(group.name, TestGroupDefinition.Name("G1"))
assertEquals(group.documentation, None)
assertEquals(group.testTags, List.empty)
assertEquals(group.testMarkers, List.empty)
assertEquals(group.beforeGroup, None)
assertEquals(group.afterGroup, None)
assertEquals(group.beforeEachTest, None)
assertEquals(group.afterEachTest, None)
assertEquals(group.tests.size, 1)
group.tests match
case t :: Nil =>
assertEquals(t.name, TestDefinition.Name("simple"))
assertEquals(t.permanentId, Ids.T1)
assertEquals(t.documentation, None)
assertEquals(t.tags, List.empty)
assertEquals(t.markers, List.empty)
assertEquals(t.iterations, TestDefinition.Iterations.One)
case _ => fail("Unexpected number of defined tests.")
}
object GroupImplementationTests:
object Ids:
val T1: PermanentId = PermanentId("t1")
end Ids
class G1 extends TestGroup.IO:
override def name: String = "G1"
test(Ids.T1, "simple").pure { Right(()) }
end G1
end GroupImplementationTests

1
project/build.properties Normal file
View file

@ -0,0 +1 @@
sbt.version=1.10.1

33
project/plugins.sbt Normal file
View file

@ -0,0 +1,33 @@
def selectCredentials(): Credentials =
if ((Path.userHome / ".sbt" / ".credentials").exists())
Credentials(Path.userHome / ".sbt" / ".credentials")
else
Credentials.apply(
realm = "Reposilite",
host = "maven.garrity.co",
userName = sys.env
.get("GS_MAVEN_USER")
.getOrElse(
throw new RuntimeException(
"You must either provide ~/.sbt/.credentials or specify the GS_MAVEN_USER environment variable."
)
),
passwd = sys.env
.get("GS_MAVEN_TOKEN")
.getOrElse(
throw new RuntimeException(
"You must either provide ~/.sbt/.credentials or specify the GS_MAVEN_TOKEN environment variable."
)
)
)
credentials += selectCredentials()
externalResolvers := Seq(
"Garrity Software Mirror" at "https://maven.garrity.co/releases",
"Garrity Software Releases" at "https://maven.garrity.co/gs"
)
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0")
addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0")
addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")