From 522bd18af1b650427d3d856eb58564a9f86901ab Mon Sep 17 00:00:00 2001 From: etorreborre Date: Thu, 20 May 2021 12:15:01 +0200 Subject: [PATCH] feature: started some reporting on unknown arguments more to be done for html and outdir arguments --- .../scala/org/specs2/main/Arguments.scala | 16 ++-- .../scala/org/specs2/main/CommandLine.scala | 80 ++++++++++++++++++- .../main/scala/org/specs2/main/Execute.scala | 21 ++++- .../main/scala/org/specs2/main/Extract.scala | 6 ++ .../main/scala/org/specs2/main/Report.scala | 21 ++++- .../main/scala/org/specs2/main/Select.scala | 9 ++- .../main/scala/org/specs2/main/Store.scala | 5 +- .../scala/org/specs2/runner/ClassRunner.scala | 3 + .../scala/org/specs2/runner/FilesRunner.scala | 17 ++-- .../specs2/runner/SpecificationsFinder.scala | 21 +---- .../scala/org/specs2/main/ArgumentsSpec.scala | 29 ++++++- 11 files changed, 183 insertions(+), 45 deletions(-) diff --git a/common/shared/src/main/scala/org/specs2/main/Arguments.scala b/common/shared/src/main/scala/org/specs2/main/Arguments.scala index 83e0128514..ada3c2fd1f 100644 --- a/common/shared/src/main/scala/org/specs2/main/Arguments.scala +++ b/common/shared/src/main/scala/org/specs2/main/Arguments.scala @@ -23,7 +23,8 @@ case class Arguments ( execute: Execute = Execute(), store: Store = Store(), report: Report = Report(), - commandLine: CommandLine = CommandLine() + commandLine: CommandLine = CommandLine(), + unknown: List[String] = List() ) extends ShowArgs { def ex: String = select.ex def include: String = select.include @@ -64,7 +65,7 @@ case class Arguments ( def isSet(a: String) = commandLine isSet a /** alias for overrideWith */ def <|(other: Arguments) = overrideWith(other) - + /** * @return a new Arguments object where the values of this are overridden with the values of other if defined */ @@ -103,10 +104,14 @@ case class Arguments ( override def toString = Seq(select, execute, report, commandLine).mkString("Arguments(", ", ", ")") + def reportUnknown(): Unit = + if (verbose && unknown.nonEmpty) + println("Unknown argument values: " + unknown.mkString(", ")) + } object Arguments extends Extract { - + /** @return new arguments from command-line arguments */ def apply(arguments: String*): Arguments = extract(CommandLine.splitValues(arguments), sysProperties) @@ -121,10 +126,11 @@ object Arguments extends Extract { execute = Execute.extract, store = Store.extract, report = Report.extract, - commandLine = CommandLine.extract + commandLine = CommandLine.extract, + unknown = CommandLine.unknownArguments ) } - + implicit def ArgumentsMonoid: Monoid[Arguments] = new Monoid[Arguments] { def append(a1: Arguments, a2: =>Arguments) = a1 overrideWith a2 val zero = Arguments() diff --git a/common/shared/src/main/scala/org/specs2/main/CommandLine.scala b/common/shared/src/main/scala/org/specs2/main/CommandLine.scala index 873b19ee88..3ef6ca49b3 100644 --- a/common/shared/src/main/scala/org/specs2/main/CommandLine.scala +++ b/common/shared/src/main/scala/org/specs2/main/CommandLine.scala @@ -4,6 +4,7 @@ package main import java.io.File import io._ +import text._ import text.Split._ /** @@ -20,7 +21,6 @@ case class CommandLine(_arguments: Seq[String] = Seq()) extends ShowArgs { * or if an attribute with that name (and any value) has been defined */ def isSet(name: String) = contains(name) || isDefined(name) - /** * @return the value for a given attribute * attribute names and values are defined in a positional way where an attribute name is always succeeded @@ -73,12 +73,86 @@ object CommandLine extends Extract { def extract(implicit arguments: Seq[String], systemProperties: SystemProperties): CommandLine = new CommandLine(_arguments = value("commandline").map(splitValues).getOrElse(Seq()) ++ arguments) - val allValueNames = Select.allValueNames ++ Store.allValueNames ++ Execute.allValueNames ++ Report.allValueNames + val allArguments: Seq[ArgumentType] = + Select.allArguments ++ + Store.allArguments ++ + Execute.allArguments ++ + Report.allArguments ++ + FilesRunnerArguments.allArguments + + val allArgumentNames = allArguments.map(_.name) def splitValues(arguments: String): Seq[String] = splitValues(arguments.split(" ")) def splitValues(arguments: Seq[String]): Seq[String] = - arguments.splitDashed(allValueNames) + arguments.splitDashed(allArgumentNames) + + // try to find if incorrect arguments have been passed on the command line + def unknownArguments(implicit arguments: Seq[String]): List[String] = { + arguments.toList match { + case List() => + List() + case name :: value :: rest => + findArgument(name) match { + case Some(BooleanArgument(_)) => + if (FromString[Boolean].fromString(value).isDefined) unknownArguments(rest) + else unknownArguments(value :: rest) + case Some(ValuedArgument(_)) => + unknownArguments(rest) + case None => + name :: unknownArguments(value :: rest) + } + case name :: _ => + findArgument(name) match { + case Some(_) => List() + case None => List(name) + } + } + } + + private def findArgument(name: String): Option[ArgumentType] = + allArguments.find { + case BooleanArgument(n) => + (name.startsWith("!") && n.toLowerCase == name.drop(1).toLowerCase) || + (n.toLowerCase == name.toLowerCase) + case ValuedArgument(n) => + n.toLowerCase == name.toLowerCase + } } +case class FilesRunnerArguments( + verbose: Boolean, + basePath: String, + glob: String, + pattern: String +) + +object FilesRunnerArguments { + /** base path for the specification files */ + val specificationsBasePath: String = + "src/test/scala" + + /** glob pattern for the file paths inside the base path */ + val specificationsPath: String = + "**/*.scala" + + /** Regex pattern used to capture a specification name in an object/class declaration */ + val specificationsPattern: String = + "(.*Spec)\\s*extends\\s*.*" + + def extract(args: Arguments): FilesRunnerArguments = + FilesRunnerArguments( + verbose = args.isSet("filesrunner.verbose"), + basePath = args.commandLine.valueOr("filesrunner.basepath", new java.io.File(specificationsBasePath).getAbsolutePath), + glob = args.commandLine.valueOr("filesrunner.path", specificationsPath), + pattern = args.commandLine.valueOr("filesrunner.pattern", specificationsPattern) + ) + + val allArguments: List[ArgumentType] = + List( + BooleanArgument("filesrunner.verbose"), + ValuedArgument("filesrunner.basepath"), + ValuedArgument("filesrunner.path"), + ValuedArgument("filesrunner.pattern")) +} diff --git a/common/shared/src/main/scala/org/specs2/main/Execute.scala b/common/shared/src/main/scala/org/specs2/main/Execute.scala index 538f2251b1..bbba74260f 100644 --- a/common/shared/src/main/scala/org/specs2/main/Execute.scala +++ b/common/shared/src/main/scala/org/specs2/main/Execute.scala @@ -104,6 +104,23 @@ object Execute extends Extract { _executor = value("executor") ) } - val allValueNames = Seq("plan", "skipAll", "stopOnFail", "stopOnError", "stopOnIssue", "stopOnSkip", "sequential", - "asap", "isolated", "useCustomClassLoader", "threadsNb", "specs2ThreadsNb", "scheduledThreadsNb", "batchSize", "timeFactor", "executor") + + val allArguments: Seq[ArgumentType] = + Seq(BooleanArgument("plan"), + BooleanArgument("skipAl"), + BooleanArgument("stopOnFail"), + BooleanArgument("stopOnError"), + BooleanArgument("stopOnIssue"), + BooleanArgument("stopOnSkip"), + BooleanArgument("sequential"), + BooleanArgument("asap"), + BooleanArgument("isolated"), + BooleanArgument("useCustomClassLoader"), + ValuedArgument("threadsNb"), + ValuedArgument("specs2ThreadsNb"), + BooleanArgument("unbatched"), + ValuedArgument("batchSize"), + ValuedArgument("timeFactor"), + ValuedArgument("timeFactor"), + ValuedArgument("executor")) } diff --git a/common/shared/src/main/scala/org/specs2/main/Extract.scala b/common/shared/src/main/scala/org/specs2/main/Extract.scala index ea155ea9c4..0ac5c2ba79 100644 --- a/common/shared/src/main/scala/org/specs2/main/Extract.scala +++ b/common/shared/src/main/scala/org/specs2/main/Extract.scala @@ -64,3 +64,9 @@ trait Extract { Classes.createInstanceFromName[T](name).runOption } + +sealed trait ArgumentType { + def name: String +} +final case class BooleanArgument(name: String) extends ArgumentType +final case class ValuedArgument(name: String) extends ArgumentType diff --git a/common/shared/src/main/scala/org/specs2/main/Report.scala b/common/shared/src/main/scala/org/specs2/main/Report.scala index b7c3fbeddb..8ddb19b09d 100644 --- a/common/shared/src/main/scala/org/specs2/main/Report.scala +++ b/common/shared/src/main/scala/org/specs2/main/Report.scala @@ -94,6 +94,23 @@ object Report extends Extract { val xonlyFlags = "#x!" val allFlags = "#1x!+-o*" - val allValueNames = Seq("showOnly", "xOnly", "failTrace", "color", "noColor", "colors", "offset", "showTimes", - "fullStackTrace", "traceFilter", "checkUrls", "noToc", "notifier", "exporter") + val allArguments: Seq[ArgumentType] = + Seq(ValuedArgument("showOnly"), + BooleanArgument("xOnly"), + BooleanArgument("failTrace"), + BooleanArgument("color"), + BooleanArgument("noColor"), + BooleanArgument("verbose"), + ValuedArgument("colors"), + BooleanArgument("showTimes"), + ValuedArgument("offset"), + ValuedArgument("smartdiffs"), + ValuedArgument("diffsclass"), + BooleanArgument("fullStackTrace"), + ValuedArgument("traceFilter"), + BooleanArgument("checkUrls"), + BooleanArgument("noToc"), + ValuedArgument("notifier"), + ValuedArgument("exporter")) + } diff --git a/common/shared/src/main/scala/org/specs2/main/Select.scala b/common/shared/src/main/scala/org/specs2/main/Select.scala index 55b50ba5e8..260809b525 100644 --- a/common/shared/src/main/scala/org/specs2/main/Select.scala +++ b/common/shared/src/main/scala/org/specs2/main/Select.scala @@ -53,5 +53,12 @@ object Select extends Extract { _selector = value("selector") ) } - val allValueNames = Seq("ex", "include", "exclude", "was", "selector") + + val allArguments: Seq[ArgumentType] = + Seq(ValuedArgument("ex"), + ValuedArgument("include"), + ValuedArgument("exclude"), + ValuedArgument("was"), + ValuedArgument("selector")) + } diff --git a/common/shared/src/main/scala/org/specs2/main/Store.scala b/common/shared/src/main/scala/org/specs2/main/Store.scala index d076939dd3..e2fb2b9672 100644 --- a/common/shared/src/main/scala/org/specs2/main/Store.scala +++ b/common/shared/src/main/scala/org/specs2/main/Store.scala @@ -32,6 +32,7 @@ object Store extends Extract { ) } - val allValueNames = Seq("resetStore", "neverStore") + val allArguments: Seq[ArgumentType] = + Seq(BooleanArgument("resetStore"), + BooleanArgument("neverStore")) } - diff --git a/core/shared/src/main/scala/org/specs2/runner/ClassRunner.scala b/core/shared/src/main/scala/org/specs2/runner/ClassRunner.scala index f2a28f6914..b4c50631f4 100644 --- a/core/shared/src/main/scala/org/specs2/runner/ClassRunner.scala +++ b/core/shared/src/main/scala/org/specs2/runner/ClassRunner.scala @@ -30,6 +30,8 @@ trait ClassRunner { */ def run(args: Array[String], exit: Boolean): Unit = { val arguments = Arguments(args.drop(1): _*) + arguments.reportUnknown() + val env = Env(arguments = arguments, lineLogger = consoleLogger) val actions: Action[Stats] = args.toList match { @@ -38,6 +40,7 @@ trait ClassRunner { Actions.ok(Stats.empty) case className :: _ => + runOperation(createSpecification(className, Thread.currentThread.getContextClassLoader, Some(env))) match { case Right(spec) => report(env)(spec) case Left(e) => diff --git a/core/shared/src/main/scala/org/specs2/runner/FilesRunner.scala b/core/shared/src/main/scala/org/specs2/runner/FilesRunner.scala index 0e3bb92261..52b906d01e 100644 --- a/core/shared/src/main/scala/org/specs2/runner/FilesRunner.scala +++ b/core/shared/src/main/scala/org/specs2/runner/FilesRunner.scala @@ -23,8 +23,9 @@ trait FilesRunner { * Run the specifications found in files based on command-line arguments */ def run(args: Array[String], exit: Boolean = false): Unit = { - val env = Env(arguments = Arguments(args: _*), - lineLogger = consoleLogger) + val arguments = Arguments(args: _*) + arguments.reportUnknown() + val env = Env(arguments = arguments, lineLogger = consoleLogger) try execute(run(env), env.arguments, exit)(env) finally env.shutdown @@ -32,13 +33,14 @@ trait FilesRunner { def run(env: Env): Action[Stats] = { val args = env.arguments - val verbose = isVerbose(args) - val base = args.commandLine.valueOr("filesrunner.basepath", new java.io.File(specificationsBasePath).getAbsolutePath) + val filesRunnerArguments = FilesRunnerArguments.extract(args) + val verbose = filesRunnerArguments.verbose + val base = filesRunnerArguments.basePath val specs = for { basePath <- Actions.checkThat(base, new java.io.File(base).isDirectory, s"$base must be a directory") ss <- findSpecifications( - glob = args.commandLine.valueOr("filesrunner.path", specificationsPath), - pattern = args.commandLine.valueOr("filesrunner.pattern", specificationsPattern), + glob = filesRunnerArguments.glob, + pattern = filesRunnerArguments.pattern, basePath = DirectoryPath.unsafe(basePath), verbose = verbose).toAction } yield ss @@ -56,9 +58,6 @@ trait FilesRunner { SpecificationStructure.topologicalSort(env)(specifications).getOrElse(specifications) } - /** @return true if the output must be verbose for debugging */ - def isVerbose(args: Arguments) = args.isSet("filesrunner.verbose") - /** print a message before the execution */ protected def beforeExecution(args: Arguments, verbose: Boolean): Operation[Unit] = for { _ <- log("\nExecuting specifications", verbose) diff --git a/core/shared/src/main/scala/org/specs2/runner/SpecificationsFinder.scala b/core/shared/src/main/scala/org/specs2/runner/SpecificationsFinder.scala index 76dd2800fd..32fb38d303 100644 --- a/core/shared/src/main/scala/org/specs2/runner/SpecificationsFinder.scala +++ b/core/shared/src/main/scala/org/specs2/runner/SpecificationsFinder.scala @@ -8,10 +8,10 @@ import specification.core._ import text.SourceFile._ import io._ import org.specs2.fp.syntax._ -import SpecificationsFinder._ import control.Operations._ import org.specs2.control.eff.Eff import org.specs2.specification.create.DefaultFragmentFactory +import main.FilesRunnerArguments._ /** * This trait loads specifications found on a given source directory based @@ -48,7 +48,7 @@ trait SpecificationsFinder { * @return specifications created from specification names */ def specifications(glob: String = "**/*.scala", - pattern: String = SpecificationsFinder.specificationsPattern, + pattern: String = specificationsPattern, filter: String => Boolean = { (name: String) => true }, basePath: DirectoryPath = DirectoryPath.unsafe(new java.io.File("src/test/scala").getAbsolutePath), verbose: Boolean = false, @@ -70,7 +70,7 @@ trait SpecificationsFinder { * a failed example is created for it */ def specificationLinks(glob: String = "**/*.scala", - pattern: String = SpecificationsFinder.specificationsPattern, + pattern: String = specificationsPattern, filter: String => Boolean = { (name: String) => true }, basePath: DirectoryPath = DirectoryPath.unsafe(new java.io.File("src/test/scala").getAbsolutePath), verbose: Boolean = false, @@ -152,17 +152,4 @@ trait SpecificationsFinder { def specPattern(specType: String, pattern: String) = "\\s*"+specType+"\\s*" + pattern } -object SpecificationsFinder extends SpecificationsFinder { - - /** base path for the specification files */ - val specificationsBasePath: String = - "src/test/scala" - - /** glob pattern for the file paths inside the base path */ - val specificationsPath: String = - "**/*.scala" - - /** Regex pattern used to capture a specification name in an object/class declaration */ - val specificationsPattern: String = - "(.*Spec)\\s*extends\\s*.*" -} +object SpecificationsFinder extends SpecificationsFinder diff --git a/core/shared/src/test/scala/org/specs2/main/ArgumentsSpec.scala b/core/shared/src/test/scala/org/specs2/main/ArgumentsSpec.scala index 85776549c1..5e585423ff 100644 --- a/core/shared/src/test/scala/org/specs2/main/ArgumentsSpec.scala +++ b/core/shared/src/test/scala/org/specs2/main/ArgumentsSpec.scala @@ -15,11 +15,11 @@ Arguments can be passed on the command line as an Array of Strings. There are 2 * string arguments which have a specific value e.g. `srcTestDir src/test` to specify the directory holding the source files - + Definition ========== - If an argument is specified, its value is returned + If an argument is specified, its value is returned + for a boolean argument like xonly the value is true + a boolean argument can be negated by adding ! in front of it. ex: `Arguments("!pandoc").commandLine.boolOr("pandoc", true) is false` @@ -73,7 +73,16 @@ Creation Arguments can be created from a sequence of strings + to declare a Notifier - """ +Unknown arguments +================= + + Unknown arguments can be detected + unknown flag $unknown1 + unknown option $unknown2 + negated boolean flag $unknown3 + with filesrunner arguments $unknown4 + +""" "values" - new group { @@ -167,5 +176,17 @@ Creation "creation" - new group { eg := Arguments("MySpec", "notifier", "IntelliJNotifier").report.notifier === "IntelliJNotifier" } -} + def unknown1 = + CommandLine.unknownArguments(Seq("xonly", "was", "x", "flag", "xonly")) === List("flag") + + def unknown2 = + CommandLine.unknownArguments(Seq("xonly", "was", "x", "option", "value", "xonly")) === List("option", "value") + + def unknown3 = + CommandLine.unknownArguments(Seq("!xonly", "was", "x")) === List() + + def unknown4 = + CommandLine.unknownArguments(Seq("filesrunner.basepath", "examples/shared/src/test/scala", "verbose", "plan", "true", "boom")) === List("boom") + +}