mirror of https://github.com/sbt/sbt.git
Added PathFinder.distinct, some API documentation, and started to clean up Path testing.
This commit is contained in:
parent
105ef1d1db
commit
48b5b5d7dc
|
|
@ -293,17 +293,30 @@ sealed abstract class PathFinder extends NotNull
|
|||
addTo(pathSet)
|
||||
wrap.Wrappers.readOnly(pathSet)
|
||||
}
|
||||
/** Only keeps paths for which `f` returns true. It is non-strict, so it is not evaluated until the returned finder is evaluated.*/
|
||||
final def filter(f: Path => Boolean): PathFinder = Path.lazyPathFinder(get.filter(f))
|
||||
/* Non-strict flatMap: no evaluation occurs until the returned finder is evaluated.*/
|
||||
final def flatMap(f: Path => PathFinder): PathFinder = Path.lazyPathFinder(get.flatMap(p => f(p).get))
|
||||
/** Evaluates this finder and converts the results to an `Array` of `URL`s..*/
|
||||
final def getURLs: Array[URL] = Path.getURLs(get)
|
||||
/** Evaluates this finder and converts the results to a `Set` of `File`s.*/
|
||||
final def getFiles: immutable.Set[File] = Path.getFiles(get)
|
||||
/** Evaluates this finder and converts the results to a `Set` of absolute path strings.*/
|
||||
final def getPaths: immutable.Set[String] = strictMap(_.absolutePath)
|
||||
/** Evaluates this finder and converts the results to a `Set` of relative path strings.*/
|
||||
final def getRelativePaths: immutable.Set[String] = strictMap(_.relativePath)
|
||||
final def strictMap[T](f: Path => T): immutable.Set[T] = Path.mapSet(get)(f)
|
||||
private[sbt] def addTo(pathSet: Set[Path])
|
||||
|
||||
/** Create a PathFinder from this one where each path has a unique name.
|
||||
* A single path is arbitrarily selected from the set of paths with the same name.*/
|
||||
def distinct: PathFinder = Path.lazyPathFinder((Map() ++ get.map(p => (p.asFile.getName, p))) .values.toList )
|
||||
|
||||
/** Constructs a string by evaluating this finder, converting the resulting Paths to absolute path strings, and joining them with the platform path separator.*/
|
||||
final def absString = Path.makeString(get)
|
||||
/** Constructs a string by evaluating this finder, converting the resulting Paths to relative path strings, and joining them with the platform path separator.*/
|
||||
final def relativeString = Path.makeRelativeString(get)
|
||||
/** Constructs a debugging string for this finder by evaluating it and separating paths by newlines.*/
|
||||
override def toString = get.mkString("\n ", "\n ","")
|
||||
}
|
||||
private class BasePathFinder(base: PathFinder) extends PathFinder
|
||||
|
|
|
|||
|
|
@ -12,46 +12,59 @@ object PathSpecification extends Properties("Path")
|
|||
{
|
||||
val log = new ConsoleLogger
|
||||
log.setLevel(Level.Warn)
|
||||
|
||||
// certain operations require a real underlying file. We'd like to create them in a managed temporary directory so that junk isn't left over from the test.
|
||||
// The arguments to several properties are functions that construct a Path or PathFinder given a base directory.
|
||||
type ToPath = ProjectDirectory => Path
|
||||
type ToFinder = ProjectDirectory => PathFinder
|
||||
|
||||
implicit val pathComponent: Arbitrary[String] =
|
||||
Arbitrary(for(id <- Gen.identifier) yield trim(id)) // TODO: make a more specific Arbitrary
|
||||
implicit val projectDirectory: Arbitrary[ProjectDirectory] = Arbitrary(Gen.value(new ProjectDirectory(new File("."))))
|
||||
implicit val arbPath: Arbitrary[Path] = Arbitrary(genPath)
|
||||
implicit val arbComponents: Arbitrary[List[String]] = Arbitrary(componentList)
|
||||
implicit val arbDup: Arbitrary[(String, Int)] = Arbitrary.arbTuple2(pathComponent, Arbitrary(Gen.choose(0, MaxDuplicates)))
|
||||
implicit val arbPath: Arbitrary[ToPath] = Arbitrary(genPath)
|
||||
implicit val arbDirs: Arbitrary[ToFinder] = Arbitrary(directories)
|
||||
|
||||
specify("Project directory relative path empty", (projectPath: ProjectDirectory) => projectPath.relativePath.isEmpty)
|
||||
specify("construction", (dir: ProjectDirectory, components: List[String]) =>
|
||||
pathForComponents(dir, components).asFile == fileForComponents(dir.asFile, components) )
|
||||
specify("Relative path", (dir: ProjectDirectory, a: List[String], b: List[String]) =>
|
||||
pathForComponents(pathForComponents(dir, a) ##, b).relativePath == pathString(b) )
|
||||
specify("Proper URL conversion", (path: Path) => path.asURL == path.asFile.toURI.toURL)
|
||||
specify("Path equality", (dir: ProjectDirectory, components: List[String]) =>
|
||||
pathForComponents(dir, components) == pathForComponents(dir, components))
|
||||
specify("Base path equality", (dir: ProjectDirectory, a: List[String], b: List[String]) =>
|
||||
pathForComponents(pathForComponents(dir, a) ##, b) == pathForComponents(pathForComponents(dir, a) ##, b) )
|
||||
specify("hashCode", (path: Path) => path.hashCode == path.asFile.hashCode)
|
||||
|
||||
// the relativize tests are a bit of a mess because of a few things:
|
||||
// 1) relativization requires directories to exist
|
||||
// 2) there is an IOException thrown in touch for paths that are too long (probably should limit the size of the Lists)
|
||||
// These problems are addressed by the helper method createFileAndDo
|
||||
|
||||
specify("relativize fail", (dir: ProjectDirectory, a: List[String], b: List[String]) =>
|
||||
{
|
||||
(!a.contains("") && !b.contains("")) ==>
|
||||
{
|
||||
createFileAndDo(a, b)
|
||||
{ dir =>
|
||||
{
|
||||
val shouldFail = (a == b) || !(b startsWith a) // will be true most of the time
|
||||
val didFail = Path.relativize(pathForComponents(dir, a), pathForComponents(dir, b)).isEmpty
|
||||
shouldFail == didFail
|
||||
}
|
||||
property("Project directory relative path empty") = secure { inTemp { dir => dir.relativePath.isEmpty } }
|
||||
property("construction") = forAll { (components: List[String]) =>
|
||||
inTemp { dir =>
|
||||
pathForComponents(dir, components).asFile == fileForComponents(dir.asFile, components)
|
||||
}
|
||||
}
|
||||
property("Relative path") = forAll { (a: List[String], b: List[String]) =>
|
||||
inTemp { dir =>
|
||||
pathForComponents(pathForComponents(dir, a) ##, b).relativePath == pathString(b) }
|
||||
}
|
||||
property("Proper URL conversion") = forAll { (tp: ToPath) =>
|
||||
withPath(tp) { path => path.asURL == path.asFile.toURI.toURL }
|
||||
}
|
||||
property("Path equality") = forAll { (components: List[String]) =>
|
||||
inTemp { dir => pathForComponents(dir, components) == pathForComponents(dir, components) }
|
||||
}
|
||||
property("Base path equality") = forAll { (a: List[String], b: List[String]) =>
|
||||
inTemp { dir =>
|
||||
pathForComponents(pathForComponents(dir, a) ##, b) == pathForComponents(pathForComponents(dir, a) ##, b)
|
||||
}
|
||||
}
|
||||
|
||||
property("hashCode") = forAll { (tp: ToPath) =>
|
||||
withPath(tp) { path =>
|
||||
path.hashCode == path.asFile.hashCode
|
||||
}
|
||||
}
|
||||
|
||||
property("relativize fail") = forAll { (a: List[String], b: List[String]) =>
|
||||
createFileAndDo(a, b)
|
||||
{ dir =>
|
||||
{
|
||||
val shouldFail = (a == b) || !(b startsWith a) // will be true most of the time
|
||||
val didFail = Path.relativize(pathForComponents(dir, a), pathForComponents(dir, b)).isEmpty
|
||||
shouldFail == didFail
|
||||
}
|
||||
}
|
||||
})
|
||||
specify("relativize", (a: List[String], b: List[String]) =>
|
||||
{
|
||||
(!b.isEmpty && !a.contains("") && !b.contains("")) ==>
|
||||
}
|
||||
property("relativize") = forAll {(a: List[String], b: List[String]) =>
|
||||
(!b.isEmpty) ==>
|
||||
{
|
||||
createFileAndDo(a, b)
|
||||
{ dir =>
|
||||
|
|
@ -62,9 +75,48 @@ object PathSpecification extends Properties("Path")
|
|||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
specify("fromString", (dir: ProjectDirectory, a: List[String]) =>
|
||||
pathForComponents(dir, a) == Path.fromString(dir, pathString(a)))
|
||||
}
|
||||
property("fromString") = forAll { (a: List[String]) =>
|
||||
inTemp { dir =>
|
||||
pathForComponents(dir, a) == Path.fromString(dir, pathString(a))
|
||||
}
|
||||
}
|
||||
|
||||
property("distinct") = forAll { (baseDirs: ToFinder, distinctNames: List[String], dupNames: List[(String, Int)]) => try {
|
||||
inTemp { dir =>
|
||||
val bases = repeat(baseDirs(dir).get)
|
||||
val reallyDistinct: Set[String] = Set() ++ distinctNames -- dupNames.map(_._1)
|
||||
val dupList = dupNames.flatMap { case (name, repeat) => if(reallyDistinct(name)) Nil else List.make(repeat, name) }
|
||||
|
||||
def create(names: List[String]): PathFinder =
|
||||
{
|
||||
val paths = (bases zip names ).map { case (a, b) => a / b }.filter(!_.exists)
|
||||
paths.foreach { f => xsbt.FileUtilities.touch(f asFile) }
|
||||
Path.lazyPathFinder(paths)
|
||||
}
|
||||
def names(s: scala.collection.Set[Path]) = s.map(_.name)
|
||||
|
||||
val distinctPaths = create(reallyDistinct.toList)
|
||||
val dupPaths = create(dupList)
|
||||
|
||||
val all = distinctPaths +++ dupPaths
|
||||
val distinct = all.distinct.get
|
||||
|
||||
val allNames = Set() ++ names(all.get)
|
||||
|
||||
(Set() ++ names(distinct)) == allNames && // verify nothing lost
|
||||
distinct.size == allNames.size // verify duplicates removed
|
||||
} } catch { case e => e.printStackTrace; throw e}
|
||||
}
|
||||
|
||||
private def repeat[T](s: Iterable[T]): List[T] =
|
||||
List.make(100, ()).flatMap(_ => s) // should be an infinite Stream, but Stream isn't very lazy
|
||||
|
||||
|
||||
private def withPath[T](tp: ToPath)(f: Path => T): T =
|
||||
inTemp { f compose tp }
|
||||
private def withPaths[T](ta: ToPath, tb: ToPath)(f: (Path, Path) => T): T =
|
||||
inTemp { dir => f(ta(dir), tb(dir)) }
|
||||
|
||||
private def createFileAndDo(a: List[String], b: List[String])(f: Path => Boolean) =
|
||||
{
|
||||
|
|
@ -83,27 +135,61 @@ object PathSpecification extends Properties("Path")
|
|||
case Right(opt) => opt.isDefined ==> opt.get
|
||||
}
|
||||
}
|
||||
private def inTemp[T](f: ProjectDirectory => T): T =
|
||||
xsbt.FileUtilities.withTemporaryDirectory { dir => f(new ProjectDirectory(dir)) }
|
||||
|
||||
private def pathString(components: List[String]): String = components.mkString(File.separator)
|
||||
private def pathForComponents(base: Path, components: List[String]): Path =
|
||||
components.foldLeft(base)((path, component) => path / component)
|
||||
components.filter(!_.isEmpty).foldLeft(base)((path, component) => path / component)
|
||||
private def fileForComponents(base: File, components: List[String]): File =
|
||||
components.foldLeft(base)((file, component) => new File(file, component))
|
||||
private def genPath: Gen[Path] =
|
||||
for(projectPath <- arbitrary[ProjectDirectory];
|
||||
a <- arbitrary[List[String]];
|
||||
b <- arbitrary[Option[List[String]]])
|
||||
yield
|
||||
{
|
||||
val base = pathForComponents(projectPath, trim(a))
|
||||
b match
|
||||
{
|
||||
case None => base
|
||||
case Some(relative) => pathForComponents(base ##, trim(relative))
|
||||
|
||||
private def paths(implicit d: Gen[ToPath], s: Gen[String]): Gen[ToPath] =
|
||||
for(dir <- d; name <- s) yield {
|
||||
(projectPath: ProjectDirectory) =>
|
||||
val f = dir(projectPath) / name
|
||||
xsbt.FileUtilities.touch(f asFile)
|
||||
f
|
||||
}
|
||||
|
||||
private def directories: Gen[ToFinder] =
|
||||
for(dirs <- directoryList) yield {
|
||||
(projectPath: ProjectDirectory) => Path.lazyPathFinder { dirs.map(_(projectPath)) }
|
||||
}
|
||||
private def directoryList: Gen[List[ToPath]] = genList(MaxDirectoryCount)(directory)
|
||||
private def directory: Gen[ToPath] =
|
||||
for(p <- genPath) yield {
|
||||
(projectPath: ProjectDirectory) => {
|
||||
val f = p(projectPath)
|
||||
xsbt.FileUtilities.createDirectory(f asFile)
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
private implicit lazy val genPath: Gen[ToPath] =
|
||||
for(a <- arbitrary[List[String]];
|
||||
b <- arbitrary[Option[List[String]]])
|
||||
yield
|
||||
(projectPath: ProjectDirectory) =>
|
||||
{
|
||||
val base = pathForComponents(projectPath, a)
|
||||
b match
|
||||
{
|
||||
case None => base
|
||||
case Some(relative) => pathForComponents(base ##, relative)
|
||||
}
|
||||
}
|
||||
private implicit lazy val componentList: Gen[List[String]] = genList[String](MaxComponentCount)(pathComponent.arbitrary)
|
||||
|
||||
private def genList[A](maxSize: Int)(implicit genA: Gen[A]) =
|
||||
for(size <- Gen.choose(0, maxSize); a <- Gen.listOfN(size, genA)) yield a
|
||||
|
||||
private def trim(components: List[String]): List[String] = components.take(MaxComponentCount)
|
||||
private def trim(component: String): String = component.substring(0, Math.min(component.length, MaxFilenameLength))
|
||||
val MaxFilenameLength = 20
|
||||
val MaxComponentCount = 6
|
||||
|
||||
final val MaxFilenameLength = 20
|
||||
final val MaxComponentCount = 6
|
||||
final val MaxDirectoryCount = 10
|
||||
final val MaxFilesCount = 100
|
||||
final val MaxDuplicates = 10
|
||||
}
|
||||
|
|
@ -74,10 +74,8 @@ object FileUtilities
|
|||
{
|
||||
createDirectory(file.getParentFile)
|
||||
val created = translate("Could not create file " + file) { file.createNewFile() }
|
||||
if(created)
|
||||
if(created || file.isDirectory)
|
||||
()
|
||||
else if(file.isDirectory)
|
||||
error("File exists and is a directory.")
|
||||
else if(!file.setLastModified(System.currentTimeMillis))
|
||||
error("Could not update last modified time for file " + file)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue