Compare commits

...

3 Commits

Author SHA1 Message Date
Patrick Stevens
57c34e0c4c Permit self-contained test fixtures (#83) 2024-06-16 21:26:31 +01:00
Patrick Stevens
7f9464b826 Args (#82) 2024-06-16 19:28:16 +01:00
Patrick Stevens
3d04199c56 More runtimes (#81) 2024-06-16 16:13:58 +01:00
3 changed files with 134 additions and 37 deletions

View File

@@ -7,11 +7,7 @@ open WoofWare.DotnetRuntimeLocator
/// Functions for locating .NET runtimes.
[<RequireQualifiedAccess>]
module DotnetRuntime =
let private selectRuntime
(config : RuntimeOptions)
(f : DotnetEnvironmentInfo)
: Choice<DotnetEnvironmentFrameworkInfo, DotnetEnvironmentSdkInfo> option
=
let private selectRuntime (config : RuntimeOptions) (f : DotnetEnvironmentInfo) : DirectoryInfo list =
let rollForward =
match Environment.GetEnvironmentVariable "DOTNET_ROLL_FORWARD" with
| null ->
@@ -20,6 +16,14 @@ module DotnetRuntime =
|> Option.defaultValue RollForward.Minor
| s -> RollForward.Parse s
if
Option.isSome config.IncludedFramework
|| Option.isSome config.IncludedFrameworks
then
// No need for a framework that's anywhere other than the given DLL.
[]
else
let desiredVersions =
match config.Framework with
| Some f -> [ Version f.Version, f.Name ]
@@ -66,15 +70,13 @@ module DotnetRuntime =
name, data.Installed
)
// TODO: how do we select between many available frameworks?
|> Seq.tryHead
|> Seq.toList
match available with
| Some (_, f) -> Some (Choice1Of2 f)
| None ->
// TODO: maybe we can ask the SDK. But we keep on trucking: maybe we're self-contained,
// and we'll actually find all the runtime next to the DLL.
None
// TODO: maybe we can ask the SDK if we don't have any runtimes.
// But we keep on trucking: maybe we're self-contained, and we'll actually find all the runtime next to the
// DLL.
available
|> List.map (fun (_name, runtime) -> DirectoryInfo $"%s{runtime.Path}/%s{runtime.Version}")
| _ -> failwith "non-minor RollForward not supported yet; please shout if you want it"
/// Given an executable DLL, locate the .NET runtime that can best run it.
@@ -96,9 +98,4 @@ module DotnetRuntime =
let runtime = selectRuntime runtimeConfig availableRuntimes
match runtime with
| None ->
// Keep on trucking: let's be optimistic and hope that we're self-contained.
[ dll.Directory ]
| Some (Choice1Of2 runtime) -> [ dll.Directory ; DirectoryInfo $"%s{runtime.Path}/%s{runtime.Version}" ]
| Some (Choice2Of2 sdk) -> [ dll.Directory ; DirectoryInfo sdk.Path ]
dll.Directory :: runtime

View File

@@ -16,6 +16,8 @@ type internal RuntimeOptions =
Tfm : string
Framework : FrameworkDescription option
Frameworks : FrameworkDescription list option
IncludedFramework : FrameworkDescription option
IncludedFrameworks : FrameworkDescription list option
RollForward : string option
}

View File

@@ -16,26 +16,97 @@ type SetBaseDir (testDll : FileInfo) =
member _.Dispose () =
AppContext.SetData ("APP_CONTEXT_BASE_DIRECTORY", oldBaseDir)
[<RequireQualifiedAccess>]
type LogLevel =
| Nothing
| Verbose
[<AutoOpen>]
module Patterns =
let (|Key|_|) (start : string) (s : string) : string option =
if s.StartsWith (start + "=", StringComparison.Ordinal) then
s.Substring (start.Length + 1) |> Some
else
None
type Args =
{
Dll : FileInfo
Trx : FileInfo option
Filter : Filter option
Logging : LogLevel
LevelOfParallelism : int option
Timeout : TimeSpan option
}
static member Parse (args : string list) : Args =
match args with
| [] -> failwith "The first arg must be a positional arg, the DLL to test."
| dll :: args ->
let rec go
(trx : FileInfo option)
(filter : Filter option)
(logging : LogLevel option)
(par : int option)
(timeout : TimeSpan option)
(args : string list)
=
match args with
| [] ->
{
Dll = FileInfo dll
Trx = trx
Filter = filter
Logging = logging |> Option.defaultValue LogLevel.Nothing
LevelOfParallelism = par
Timeout = timeout
}
| Key "--filter" filterStr :: rest
| "--filter" :: filterStr :: rest ->
match filter with
| Some _ -> failwith "Two conflicting filters; you can only specify --filter once"
| None -> go trx (Some (Filter.parse filterStr)) logging par timeout rest
| Key "--trx" trxStr :: rest
| "--trx" :: trxStr :: rest ->
match trx with
| Some _ -> failwith "Two conflicting TRX outputs; you can only specify --trx once"
| None -> go (Some (FileInfo trxStr)) filter logging par timeout rest
| Key "--verbose" verboseStr :: rest
| "--verbose" :: verboseStr :: rest ->
match logging with
| Some _ -> failwith "Two conflicting --verbose outputs; you can only specify --verbose once"
| None ->
let verbose =
if Boolean.Parse verboseStr then
LogLevel.Verbose
else
LogLevel.Nothing
go trx filter (Some verbose) par timeout rest
| Key "--parallelism" parStr :: rest
| "--parallelism" :: parStr :: rest ->
match par with
| Some _ -> failwith "Two conflicting --parallelism outputs; you can only specify --parallelism once"
| None -> go trx filter logging (Some (Int32.Parse parStr)) timeout rest
| Key "--timeout-seconds" timeoutStr :: rest
| "--timeout-seconds" :: timeoutStr :: rest ->
match timeout with
| Some _ ->
failwith "Two conflicting --timeout-seconds outputs; you can only specify --timeout-seconds once"
| None -> go trx filter logging par (Some (TimeSpan.FromSeconds (Int32.Parse timeoutStr |> float))) rest
| k :: _rest -> failwith $"Unrecognised arg %s{k}"
go None None None None None args
module Program =
let main argv =
let startTime = DateTimeOffset.Now
let testDll, filter, trxPath =
match argv |> List.ofSeq with
| [ dll ] -> FileInfo dll, None, None
| [ dll ; "--trx" ; trxPath ] -> FileInfo dll, None, Some (FileInfo trxPath)
| [ dll ; "--filter" ; filter ] -> FileInfo dll, Some (Filter.parse filter), None
| [ dll ; "--trx" ; trxPath ; "--filter" ; filter ] ->
FileInfo dll, Some (Filter.parse filter), Some (FileInfo trxPath)
| [ dll ; "--filter" ; filter ; "--trx" ; trxPath ] ->
FileInfo dll, Some (Filter.parse filter), Some (FileInfo trxPath)
| _ ->
failwith
"provide exactly one arg, a test DLL; then optionally `--filter <filter>` and/or `--trx <output-filename>`."
let args = argv |> List.ofArray |> Args.Parse
let filter =
match filter with
match args.Filter with
| Some filter -> Filter.shouldRun filter
| None -> fun _ _ -> true
@@ -46,12 +117,20 @@ module Program =
let progress = Progress.spectre stderr
use _ = new SetBaseDir (testDll)
let runtime = DotnetRuntime.locate args.Dll
match args.Logging with
| LogLevel.Nothing -> ()
| LogLevel.Verbose ->
for d in runtime do
stderr.WriteLine $".NET runtime directory: %s{d.FullName}"
use _ = new SetBaseDir (args.Dll)
use contexts = TestContexts.Empty ()
let ctx = LoadContext (testDll, DotnetRuntime.locate testDll, contexts)
let assy = ctx.LoadFromAssemblyPath testDll.FullName
let ctx = LoadContext (args.Dll, runtime, contexts)
let assy = ctx.LoadFromAssemblyPath args.Dll.FullName
let levelOfParallelism, par =
((None, None), assy.CustomAttributes)
@@ -92,6 +171,20 @@ module Program =
| _ -> levelPar, par
)
let levelOfParallelism =
match args.LevelOfParallelism, levelOfParallelism with
| None, None -> None
| Some taken, Some ignored ->
match args.Logging with
| LogLevel.Nothing -> ()
| LogLevel.Verbose ->
stderr.WriteLine
$"Taking parallelism %i{taken} from command line, ignoring value %i{ignored} from assembly"
Some taken
| Some x, None
| None, Some x -> Some x
let testFixtures = assy.ExportedTypes |> Seq.map TestFixture.parse |> Seq.toList
use par = new ParallelQueue (levelOfParallelism, par)
@@ -103,7 +196,12 @@ module Program =
|> List.map (TestFixture.run contexts par progress filter)
|> Task.WhenAll
if not (results.Wait (TimeSpan.FromHours 2.0)) then
let timeout =
match args.Timeout with
| None -> TimeSpan.FromHours 2.0
| Some t -> t
if not (results.Wait timeout) then
failwith "Tests failed to terminate within two hours"
let results = results.Result |> Seq.concat |> List.ofSeq
@@ -336,7 +434,7 @@ module Program =
ResultsSummary = resultSummary
}
match trxPath with
match args.Trx with
| Some trxPath ->
let contents = TrxReport.toXml report |> fun d -> d.OuterXml
trxPath.Directory.Create ()