diff --git a/main/src/test/scala/ParseKey.scala b/main/src/test/scala/ParseKey.scala index 7e348ab73..953d4bf5d 100644 --- a/main/src/test/scala/ParseKey.scala +++ b/main/src/test/scala/ParseKey.scala @@ -25,49 +25,66 @@ import sbt.librarymanagement.Configuration * This includes properly resolving omitted components. */ object ParseKey extends Properties("Key parser test") { - property("An explicitly specified axis is always parsed to that explicit value") = forAll { - (skm: StructureKeyMask) => - import skm.{ structure, key } - val hasZeroConfig = key.scope.config == Zero - val mask = if (hasZeroConfig) skm.mask.copy(project = true) else skm.mask - // Note that this explicitly displays the configuration axis set to Zero. - // This is to disambiguate `proj/Zero/name`, which could render potentially - // as `Zero/name`, but could be interpreted as `Zero/Zero/name`. - val expected = resolve(structure, key, mask) - parseCheck(structure, key, mask, hasZeroConfig)( - sk => - Project.equal(sk, expected, mask) - :| s"$sk.key == $expected.key: ${sk.key == expected.key}" - :| s"${sk.scope} == ${expected.scope}: ${Scope.equal(sk.scope, expected.scope, mask)}" - ) :| s"Expected: ${displayFull(expected)}" + propertyWithSeed("An explicitly specified axis is always parsed to that explicit value", None) = + forAll(roundtrip(_)) + + def roundtrip(skm: StructureKeyMask) = { + import skm.{ structure, key } + + // if the configuration axis == Zero + // then a scoped key like `proj/Zero/Zero/name` could render potentially as `Zero/name` + // which would be interpreted as `Zero/Zero/Zero/name` (Global/name) + // so we mitigate this by explicitly displaying the configuration axis set to Zero + val hasZeroConfig = key.scope.config == Zero + + val showZeroConfig = hasZeroConfig || hasAmbiguousLowercaseAxes(key) + val mask = if (showZeroConfig) skm.mask.copy(project = true) else skm.mask + + val expected = resolve(structure, key, mask) + parseCheck(structure, key, mask, showZeroConfig)( + sk => + Project.equal(sk, expected, mask) + :| s"$sk.key == $expected.key: ${sk.key == expected.key}" + :| s"${sk.scope} == ${expected.scope}: ${Scope.equal(sk.scope, expected.scope, mask)}" + ) :| s"Expected: ${displayFull(expected)}" } - property("An unspecified project axis resolves to the current project") = forAll { - (skm: StructureKeyMask) => - import skm.{ structure, key } - val mask = skm.mask.copy(project = false) - // skip when config axis is set to Zero - val hasZeroConfig = key.scope.config == Zero - parseCheck(structure, key, mask)( - sk => - (hasZeroConfig || sk.scope.project == Select(structure.current)) - :| s"Current: ${structure.current}" - ) + propertyWithSeed("An unspecified project axis resolves to the current project", None) = forAll( + noProject(_) + ) + + def noProject(skm: StructureKeyMask) = { + import skm.{ structure, key } + val mask = skm.mask.copy(project = false) + // skip when config axis is set to Zero + val hasZeroConfig = key.scope.config == Zero + val showZeroConfig = hasAmbiguousLowercaseAxes(key) + parseCheck(structure, key, mask, showZeroConfig)( + sk => + (hasZeroConfig || sk.scope.project == Select(structure.current)) + :| s"Current: ${structure.current}" + ) } - property("An unspecified task axis resolves to Zero") = forAll { (skm: StructureKeyMask) => + propertyWithSeed("An unspecified task axis resolves to Zero", None) = forAll(noTask(_)) + + def noTask(skm: StructureKeyMask) = { import skm.{ structure, key } val mask = skm.mask.copy(task = false) parseCheck(structure, key, mask)(_.scope.task == Zero) } - property( - "An unspecified configuration axis resolves to the first configuration directly defining the key or else Zero" - ) = forAll { (skm: StructureKeyMask) => + propertyWithSeed( + "An unspecified configuration axis resolves to the first configuration directly defining the key or else Zero", + None + ) = forAll(noConfig(_)) + + def noConfig(skm: StructureKeyMask) = { import skm.{ structure, key } val mask = ScopeMask(config = false) val resolvedConfig = Resolve.resolveConfig(structure.extra, key.key, mask)(key.scope).config - parseCheck(structure, key, mask)( + val showZeroConfig = hasAmbiguousLowercaseAxes(key) + parseCheck(structure, key, mask, showZeroConfig)( sk => (sk.scope.config == resolvedConfig) || (sk.scope == Scope.GlobalScope) ) :| s"Expected configuration: ${resolvedConfig map (_.name)}" } @@ -350,4 +367,12 @@ object ParseKey extends Properties("Key parser test") { case (delegates, key) => Taskk(key, delegates) } } + + // if both a project and a key share the same name (e.g. "foo") + // then a scoped key like `foo//foo/name` would render as `foo/name` + // which would be interpreted as `foo/Zero/Zero/name` + // so we mitigate this by explicitly displaying the configuration axis set to Zero + def hasAmbiguousLowercaseAxes(key: ScopedKey[_]) = PartialFunction.cond(key.scope) { + case Scope(Select(ProjectRef(_, proj)), _, Select(key), _) => proj == key.label + } }