Skip to content

Commit d0d8a51

Browse files
committed
Add tests for InputsComposer
1 parent f1d4d97 commit d0d8a51

File tree

4 files changed

+159
-61
lines changed

4 files changed

+159
-61
lines changed

modules/build/src/main/scala/scala/build/input/InputsComposer.scala

Lines changed: 68 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,84 @@ import scala.collection.mutable
1313

1414
object InputsComposer {
1515

16-
private object Keys {
16+
private[input] object Keys {
1717
val modules = "modules"
1818
val roots = "roots"
1919
val dependsOn = "dependsOn"
2020
}
2121

22-
private case class ModuleDefinition(
22+
private[input] case class ModuleDefinition(
2323
name: String,
2424
roots: Seq[String],
2525
dependsOn: Seq[String] = Nil
2626
)
27+
28+
// TODO Check for module dependencies that do not exist
29+
private[input] def readAllModules(modules: Option[Value])
30+
: Either[BuildException, Seq[ModuleDefinition]] = modules match {
31+
case Some(Tbl(values)) => EitherSequence.sequence {
32+
values.toSeq.map(readModule)
33+
}.left.map(CompositeBuildException.apply)
34+
case _ => Left(ModuleConfigurationError(s"$modules must exist and must be a table"))
35+
}
36+
37+
private def readModule(
38+
key: String,
39+
value: Value
40+
): Either[ModuleConfigurationError, ModuleDefinition] = {
41+
value match
42+
case Tbl(values) =>
43+
val maybeRoots = values.get(Keys.roots).map {
44+
case Str(value) => Right(Seq(value))
45+
case Arr(values) => EitherSequence.sequence {
46+
values.map {
47+
case Str(value) => Right(value)
48+
case _ => Left(())
49+
}
50+
}.left.map(_ => ())
51+
case _ => Left(())
52+
}.getOrElse(Right(Seq(key)))
53+
.left.map(_ =>
54+
ModuleConfigurationError(
55+
s"${Keys.modules}.$key.${Keys.roots} must be a string or a list of strings"
56+
)
57+
)
58+
59+
val maybeDependsOn = values.get(Keys.dependsOn).map {
60+
case Arr(values) =>
61+
EitherSequence.sequence {
62+
values.map {
63+
case Str(value) => Right(value)
64+
case _ => Left(())
65+
}
66+
}.left.map(_ => ())
67+
case _ => Left(())
68+
}.getOrElse(Right(Nil))
69+
.left.map(_ =>
70+
ModuleConfigurationError(
71+
s"${Keys.modules}.$key.${Keys.dependsOn} must be a list of strings"
72+
)
73+
)
74+
75+
for {
76+
roots <- maybeRoots
77+
dependsOn <- maybeDependsOn
78+
} yield ModuleDefinition(key, roots, dependsOn)
79+
80+
case _ => Left(ModuleConfigurationError(s"${Keys.modules}.$key must be a table"))
81+
}
2782
}
2883

84+
/** Creates [[ModuleInputs]] given the initial arguments passed to the command,
85+
* Looks for module config .toml file and if found composes module inputs according to the defined config,
86+
* otherwise if module config is not found or if [[allowForbiddenFeatures]] is not set, returns only one basic module created from initial args (see [[basicInputs]])
87+
*
88+
* @param args - initial args passed to command
89+
* @param cwd - working directory
90+
* @param inputsFromArgs - function that proceeds with the whole [[ModuleInputs]] creation flow (validating elements, etc.) this takes into account options passed from CLI
91+
* like in SharedOptions
92+
* @param allowForbiddenFeatures
93+
*/
2994
final case class InputsComposer(
3095
args: Seq[String],
3196
cwd: os.Path,
@@ -88,60 +153,6 @@ final case class InputsComposer(
88153
} yield fromArgs.orElse(fromCwd)
89154
}
90155

91-
// TODO Check for module dependencies that do not exist
92-
private def readAllModules(modules: Option[Value])
93-
: Either[BuildException, Seq[ModuleDefinition]] = modules match {
94-
case Some(Tbl(values)) => EitherSequence.sequence {
95-
values.toSeq.map(readModule)
96-
}.left.map(CompositeBuildException.apply)
97-
case _ => Left(ModuleConfigurationError(s"$modules must exist and must be a table"))
98-
}
99-
100-
private def readModule(
101-
key: String,
102-
value: Value
103-
): Either[ModuleConfigurationError, ModuleDefinition] =
104-
value match
105-
case Tbl(values) =>
106-
val maybeRoots = values.get(Keys.roots).map {
107-
case Str(value) => Right(Seq(value))
108-
case Arr(values) => EitherSequence.sequence {
109-
values.map {
110-
case Str(value) => Right(value)
111-
case _ => Left(())
112-
}
113-
}.left.map(_ => ())
114-
case _ => Left(())
115-
}.getOrElse(Right(Seq(key)))
116-
.left.map(_ =>
117-
ModuleConfigurationError(
118-
s"${Keys.modules}.$key.${Keys.roots} must be a string or a list of strings"
119-
)
120-
)
121-
122-
val maybeDependsOn = values.get(Keys.dependsOn).map {
123-
case Arr(values) =>
124-
EitherSequence.sequence {
125-
values.map {
126-
case Str(value) => Right(value)
127-
case _ => Left(())
128-
}
129-
}.left.map(_ => ())
130-
case _ => Left(())
131-
}.getOrElse(Right(Nil))
132-
.left.map(_ =>
133-
ModuleConfigurationError(
134-
s"${Keys.modules}.$key.${Keys.dependsOn} must be a list of strings"
135-
)
136-
)
137-
138-
for {
139-
roots <- maybeRoots
140-
dependsOn <- maybeDependsOn
141-
} yield ModuleDefinition(key, roots, dependsOn)
142-
143-
case _ => Left(ModuleConfigurationError(s"${Keys.modules}.$key must be a table"))
144-
145156
private def checkForCycles(modules: Seq[ModuleDefinition])
146157
: Either[ModuleConfigurationError, Unit] = either {
147158
val lookup = Map.from(modules.map(module => module.name -> module))
@@ -190,7 +201,7 @@ final case class InputsComposer(
190201
val moduleDeps: Seq[ProjectName] = moduleDef.dependsOn.map(projectNameMap)
191202

192203
inputs.dependsOn(moduleDeps)
193-
inputs.withForcedWorkspace(moduleConfigPath / os.up)
204+
.withForcedWorkspace(moduleConfigPath / os.up)
194205
}
195206

196207
moduleInputs
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package scala.build.input
2+
3+
import scala.build.Build
4+
import scala.build.errors.BuildException
5+
import scala.build.internal.Constants
6+
import scala.build.options.BuildOptions
7+
import scala.build.tests.{TestInputs, TestUtil}
8+
9+
class InputsComposerTest extends TestUtil.ScalaCliBuildSuite {
10+
11+
test("read simple module config") {
12+
val configText =
13+
"""[modules.webpage]
14+
|dependsOn = ["core"]
15+
|
16+
|[modules.core]
17+
|roots = ["Core.scala", "Utils.scala"]
18+
|""".stripMargin
19+
20+
val parsedModules = {
21+
for {
22+
table <- toml.Toml.parse(configText)
23+
modules <- InputsComposer.readAllModules(table.values.get(InputsComposer.Keys.modules))
24+
} yield modules
25+
}.toSeq.flatten
26+
27+
assert(parsedModules.nonEmpty)
28+
29+
assert(parsedModules.head.name == "webpage")
30+
val webpageModule = parsedModules.head
31+
assert(webpageModule.roots.toSet == Set("webpage"))
32+
assert(webpageModule.dependsOn.toSet == Set("core"))
33+
34+
val coreModule = parsedModules.last
35+
assert(coreModule.name == "core")
36+
assert(coreModule.roots.toSet == Set("Core.scala", "Utils.scala"))
37+
assert(coreModule.dependsOn.isEmpty)
38+
}
39+
40+
test("compose module inputs from module config") {
41+
val testInputs = TestInputs(
42+
os.rel / Constants.moduleConfigFileName ->
43+
"""[modules.webpage]
44+
|dependsOn = ["core"]
45+
|
46+
|[modules.core]
47+
|roots = ["Core.scala", "Utils.scala"]
48+
|""".stripMargin
49+
)
50+
51+
testInputs.fromRoot { root =>
52+
val argsToInputs: Seq[String] => Either[BuildException, ModuleInputs] = args => {
53+
val emptyInputs = ModuleInputs.empty(args.head)
54+
Right(Build.updateInputs(emptyInputs, BuildOptions()))
55+
}
56+
val modules = InputsComposer(Seq(root.toString), root, argsToInputs, true)
57+
.getModuleInputs
58+
.toSeq
59+
.flatten
60+
61+
assert(modules.nonEmpty)
62+
assert(modules.head.baseProjectName.startsWith("webpage"), clue = modules.head.baseProjectName)
63+
64+
val websiteModule = modules.head
65+
val coreModule = modules.last
66+
val coreProjectName = coreModule.projectName
67+
68+
assert(websiteModule.moduleDependencies.toSet == Set(coreProjectName))
69+
}
70+
}
71+
}

modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package scala.cli.integration
22

3-
import ch.epfl.scala.bsp4j.JvmTestEnvironmentParams
3+
import ch.epfl.scala.bsp4j.{BuildTargetEvent, JvmTestEnvironmentParams}
44
import ch.epfl.scala.bsp4j as b
55
import com.eed3si9n.expecty.Expecty.expect
66
import com.google.gson.{Gson, JsonElement}
77

88
import java.net.URI
99
import java.nio.file.Paths
10-
1110
import scala.async.Async.{async, await}
1211
import scala.concurrent.ExecutionContext.Implicits.global
1312
import scala.concurrent.Future
@@ -642,7 +641,12 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
642641
val changes = didChangeParams.getChanges.asScala.toSeq
643642
expect(changes.length == 2)
644643

645-
val change = changes.head
644+
val change: BuildTargetEvent = {
645+
val targets = changes.map(_.getTarget)
646+
expect(targets.length == 2)
647+
val mainTarget = extractMainTargets(targets)
648+
changes.find(_.getTarget == mainTarget).get
649+
}
646650
expect(change.getTarget.getUri == targetUri)
647651
expect(change.getKind == b.BuildTargetEventKind.CHANGED)
648652

@@ -758,7 +762,12 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
758762
val changes = didChangeParams.getChanges.asScala.toSeq
759763
expect(changes.length == 2)
760764

761-
val change = changes.head
765+
val change: BuildTargetEvent = {
766+
val targets = changes.map(_.getTarget)
767+
expect(targets.length == 2)
768+
val mainTarget = extractMainTargets(targets)
769+
changes.find(_.getTarget == mainTarget).get
770+
}
762771
expect(change.getTarget.getUri == targetUri)
763772
expect(change.getKind == b.BuildTargetEventKind.CHANGED)
764773
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//package scala.cli.integration.compose
2+
//
3+
//import scala.cli.integration.{BspTestDefinitions, ScalaCliSuite}
4+
//
5+
//trait ComposeBspTestDefinitions extends ScalaCliSuite { _: BspTestDefinitions =>
6+
// test()
7+
//}

0 commit comments

Comments
 (0)