diff --git a/.fantomasignore b/.fantomasignore index 01a3035b..ea4b0bf9 100644 --- a/.fantomasignore +++ b/.fantomasignore @@ -1,3 +1,4 @@ # Ignore interfaces (documentation annotations) *.fsi -Abstract.fs \ No newline at end of file +Abstract.fs +Recorder.fs \ No newline at end of file diff --git a/AltCover.Engine/CecilEx.fs b/AltCover.Engine/CecilEx.fs index fb7927fb..e2b8e1bd 100644 --- a/AltCover.Engine/CecilEx.fs +++ b/AltCover.Engine/CecilEx.fs @@ -140,21 +140,22 @@ type internal AssemblyResolver() as self = let sources = [ AssemblyConstants.packageEnv - [ Environment.GetEnvironmentVariable "ProgramFiles" - |> Option.ofObj - |> Option.map (fun p -> Path.Combine(p, dotnetShared)) - Some <| Path.Combine(share, dotnetShared) - Some <| Path.Combine(shareLocal, dotnetShared) - AssemblyConstants.dotnetDir - |> Option.map (fun p -> Path.Combine(p, "shared")) - Some AssemblyConstants.nugetCache + [ Some AssemblyConstants.nugetCache Some <| Path.Combine("usr", monogac) Environment.GetEnvironmentVariable "WinDir" |> Option.ofObj |> Option.map (fun p -> Path.Combine(p, wingac)) + Some <| Path.Combine("usr", monogac) Environment.GetEnvironmentVariable "MONO_GAC_PREFIX" |> Option.ofObj - |> Option.map (fun p -> Path.Combine(p, monogac)) ] + |> Option.map (fun p -> Path.Combine(p, monogac)) + Environment.GetEnvironmentVariable "ProgramFiles" + |> Option.ofObj + |> Option.map (fun p -> Path.Combine(p, dotnetShared)) + Some <| Path.Combine(share, dotnetShared) + Some <| Path.Combine(shareLocal, dotnetShared) + AssemblyConstants.dotnetDir + |> Option.map (fun p -> Path.Combine(p, "shared")) ] |> List.choose id ] |> List.concat |> List.filter Directory.Exists diff --git a/AltCover.Engine/CommandLine.fs b/AltCover.Engine/CommandLine.fs index ac31ac2d..33328f3b 100644 --- a/AltCover.Engine/CommandLine.fs +++ b/AltCover.Engine/CommandLine.fs @@ -70,8 +70,8 @@ module internal Zip = [] - let internal openUpdate (report: string) = - if File.Exists(report + ".zip") then + let internal openUpdate (report: string) (zipped: bool) = + if zipped then let zip = ZipFile.Open(report + ".zip", ZipArchiveMode.Update) @@ -85,7 +85,7 @@ module internal Zip = new MemoryStream() :> Stream (zip, stream) - else if File.Exists report then + else let stream = new FileStream( report, @@ -97,8 +97,14 @@ module internal Zip = ) (null, stream :> Stream) - else - (null, new MemoryStream() :> Stream) + + let internal openUpdateReport format (report: string) (zipped: bool) = + let container, stream = + openUpdate report zipped + + use _ = container + use _ = stream + DocumentType.LoadReportStream format stream type internal StringSink = Action @@ -496,6 +502,6 @@ module internal CommandLine = "InstantiateArgumentExceptionCorrectlyRule", Scope = "member", // MethodDefinition Target = - "AltCover.CommandLine/I/transform@288::Invoke(System.String[])", + "AltCover.CommandLine/I/transform@294::Invoke(System.String[])", Justification = "Inlined library code")>] () \ No newline at end of file diff --git a/AltCover.Engine/Main.fs b/AltCover.Engine/Main.fs index f73120a8..3aeb370e 100644 --- a/AltCover.Engine/Main.fs +++ b/AltCover.Engine/Main.fs @@ -703,10 +703,11 @@ module internal Main = |> Option.defaultValue false let internal selectReportGenerator () = - match CoverageParameters.reportKind () with - | ReportFormat.OpenCoverWithTracking + match + CoverageParameters.reportKind () + &&& ReportFormat.TrackMask + with | ReportFormat.OpenCover -> OpenCover.reportGenerator () - | ReportFormat.NativeJsonWithTracking | ReportFormat.NativeJson -> NativeJson.reportGenerator () | _ -> Report.reportGenerator () diff --git a/AltCover.Engine/NativeJson.fs b/AltCover.Engine/NativeJson.fs index b13a3479..6dd69ed7 100644 --- a/AltCover.Engine/NativeJson.fs +++ b/AltCover.Engine/NativeJson.fs @@ -1235,10 +1235,15 @@ module x #endif - let internal fileToJson filename = +#if GUI + let fileToJson filename = filename |> File.ReadAllText |> fromJsonText - +#else // RUNNER + let internal streamToJson (stream: Stream) = + use r = new StreamReader(stream) + r.ReadToEnd() |> fromJsonText #endif +#endif // GUI || RUNNER #if RUNNER // Instrumentation --------------------------------------------------------- @@ -1421,17 +1426,27 @@ type internal DocumentType = | XML of XDocument | JSON of NativeJson.Modules | Unknown - static member internal LoadReport format report = - if File.Exists report then - if - format = ReportFormat.NativeJson - || format = ReportFormat.NativeJsonWithTracking - then - report |> NativeJson.fileToJson |> JSON + //static member internal LoadReport format report = + // if File.Exists report then + // if + // format = ReportFormat.NativeJson + // || format = ReportFormat.NativeJsonWithTracking + // then + // report |> NativeJson.fileToJson |> JSON + // else + // report |> XDocument.Load |> XML + // else + // Unknown + + static member internal LoadReportStream format (report: Stream) = + if report.Length > 0 then + if format &&& ReportFormat.TrackMask = ReportFormat.NativeJson then + report |> NativeJson.streamToJson |> JSON else report |> XDocument.Load |> XML else Unknown + #endif #if GUI || RUNNER diff --git a/AltCover.Engine/PostProcess.fs b/AltCover.Engine/PostProcess.fs index 1fd26f53..3d9d4c6a 100644 --- a/AltCover.Engine/PostProcess.fs +++ b/AltCover.Engine/PostProcess.fs @@ -241,8 +241,7 @@ module internal PostProcess = format (document: XmlAbstraction) = - match format with - | ReportFormat.OpenCoverWithTracking + match format &&& ReportFormat.TrackMask with | ReportFormat.OpenCover -> if counts.ContainsKey Track.Entry then fillTrackedVisits document counts.[Track.Entry] "entry" @@ -514,6 +513,6 @@ module internal PostProcess = "PreferStringComparisonOverrideRule", Scope = "member", Target = - "AltCover.PostProcess/Pipe #2 stage #1 at line 331@332-1::Invoke(AltCover.XmlElementAbstraction)", + "AltCover.PostProcess/Pipe #2 stage #1 at line 330@331-1::Invoke(AltCover.XmlElementAbstraction)", Justification = "Compiler generated")>] () \ No newline at end of file diff --git a/AltCover.Engine/Runner.fs b/AltCover.Engine/Runner.fs index 4a7dc9e9..4632c595 100644 --- a/AltCover.Engine/Runner.fs +++ b/AltCover.Engine/Runner.fs @@ -1328,11 +1328,13 @@ module internal Runner = Justification = "meets an interface")>] let writeNativeJsonReport (hits: Dictionary>) - unusedCannotBeUnderscore + format (file: Stream) output = - ignore unusedCannotBeUnderscore + let zipped = + int (format &&& ReportFormat.Zipped) <> 0 + let flushStart = DateTime.UtcNow // do work here let jsonText = @@ -1357,17 +1359,8 @@ module internal Runner = NativeJson.serializeToUtf8Bytes json if Option.isSome output then - use outputFile = - new FileStream( - output.Value, - FileMode.OpenOrCreate, - FileAccess.Write, - FileShare.None, - 4096, - FileOptions.SequentialScan - ) - - outputFile.Write(encoded, 0, encoded.Length) + Zip.save (fun s -> s.Write(encoded, 0, encoded.Length)) output.Value zipped + else file.Seek(0L, SeekOrigin.Begin) |> ignore file.SetLength 0L @@ -1381,16 +1374,24 @@ module internal Runner = report = let reporter (arg: string option) = + let zipped = + int (format &&& ReportFormat.Zipped) <> 0 + let (container, file) = - Zip.openUpdate report - - try - if - format = ReportFormat.NativeJson - || format = ReportFormat.NativeJsonWithTracking - then - writeNativeJsonReport hits format file arg - else + Zip.openUpdate report zipped + + use container' = container + use file' = file + + if format &&& ReportFormat.TrackMask = ReportFormat.NativeJson then + writeNativeJsonReport hits format file arg + else + use outputFile = + match arg with + | None -> file + | _ -> new MemoryStream() :> Stream + + let result = AltCover.Counter.doFlushStream (postProcess hits format) pointProcess @@ -1398,12 +1399,15 @@ module internal Runner = hits format file - arg - finally - file.Dispose() + outputFile - if container.IsNotNull then - container.Dispose() + match arg with + | None -> () + | Some x -> + outputFile.Position <- 0l + Zip.save (outputFile.CopyTo) x zipped + + result reporter @@ -1513,8 +1517,11 @@ module internal Runner = ) |> Seq.iter File.Delete + let zipped = + int (format &&& ReportFormat.Zipped) <> 0 + let document = - DocumentType.LoadReport format report + Zip.openUpdateReport format (Option.defaultValue report output) zipped J.doSummaries document format result) 255 diff --git a/AltCover.Engine/Visitor.fs b/AltCover.Engine/Visitor.fs index 136f501e..ce11fddf 100644 --- a/AltCover.Engine/Visitor.fs +++ b/AltCover.Engine/Visitor.fs @@ -412,27 +412,35 @@ module internal CoverageParameters = else path - let internal reportFormat () = + let internal reportFormat0 () = let fmt = reportKind () if fmt = ReportFormat.OpenCover && (trackingNames.Any() || interval () > 0) then - ReportFormat.OpenCoverWithTracking + ReportFormat.OpenCover + ||| ReportFormat.WithTracking else if fmt = ReportFormat.NativeJson && (trackingNames.Any() || interval () > 0) then - ReportFormat.NativeJsonWithTracking + ReportFormat.NativeJson + ||| ReportFormat.WithTracking else fmt + let internal reportFormat () = + let raw = reportFormat0 () + + if zipReport.Value then + raw ||| ReportFormat.Zipped + else + raw + let internal isTracking () = - match reportFormat () with - | ReportFormat.OpenCoverWithTracking - | ReportFormat.NativeJsonWithTracking -> true - | _ -> false + int (reportFormat () &&& ReportFormat.WithTracking) + <> 0 let withBranches () = reportFormat () <> ReportFormat.NCover @@ -1674,30 +1682,30 @@ module internal Visitor = "AvoidMessageChainsRule", Scope = "member", Target = - "AltCover.Visitor/I/generated@1403::Invoke(Mono.Cecil.Cil.Instruction)", + "AltCover.Visitor/I/generated@1411::Invoke(Mono.Cecil.Cil.Instruction)", Justification = "No direct call available")>] [,Microsoft.FSharp.Collections.FSharpList`1)", + "AltCover.Visitor/I/start@1257::Invoke(Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Collections.FSharpList`1)", Justification = "Inlined library code")>] [,Microsoft.FSharp.Collections.FSharpList`1)", + "AltCover.Visitor/I/finish@1260::Invoke(Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Collections.FSharpList`1)", Justification = "Inlined library code")>] [] [] () \ No newline at end of file diff --git a/AltCover.Recorder.Tests/Recorder.Tests.fs b/AltCover.Recorder.Tests/Recorder.Tests.fs index d56832d1..d9143889 100644 --- a/AltCover.Recorder.Tests/Recorder.Tests.fs +++ b/AltCover.Recorder.Tests/Recorder.Tests.fs @@ -93,7 +93,15 @@ module AltCoverTests = let tracer = Adapter.makeNullTrace String.Empty - Assert.True(tracer.GetType().Assembly.GetName().Name = "AltCover.Recorder") + // whitelist test not recorder.g + + let n = + tracer.GetType().Assembly.GetName().Name +#if RECORDERMODERN + Assert.That(n, Is.EqualTo "AltCover.RecorderModern") +#else + Assert.That(n, Is.EqualTo "AltCover.Recorder") +#endif getMyMethodName "<=" [] @@ -138,6 +146,7 @@ module AltCoverTests = getMyMethodName "<=" + [] let RealIdShouldIncrementCount () = getMyMethodName "=>" @@ -154,7 +163,11 @@ module AltCoverTests = Instance.I.recording <- true Instance.CoverageFormat <- ReportFormat.NCover Instance.Visit key -23 - Instance.CoverageFormat <- ReportFormat.OpenCoverWithTracking + + Instance.CoverageFormat <- + ReportFormat.OpenCover + ||| ReportFormat.WithTracking + Instance.Visit key -23 let vs = Adapter.VisitsSeq() @@ -191,7 +204,11 @@ module AltCoverTests = let PayloadGeneratedIsAsExpected () = try Instance.I.isRunner <- false - Instance.CoverageFormat <- ReportFormat.OpenCoverWithTracking + + Instance.CoverageFormat <- + ReportFormat.OpenCover + ||| ReportFormat.WithTracking + Assert.True(Instance.I.callerId () |> Option.isNone) Assert.True(Adapter.payloadSelector false = Adapter.asNull ()) Assert.True(Adapter.payloadSelector true = Adapter.asNull ()) @@ -248,7 +265,11 @@ module AltCoverTests = try Instance.I.isRunner <- true - Instance.CoverageFormat <- ReportFormat.OpenCoverWithTracking + + Instance.CoverageFormat <- + ReportFormat.OpenCover + ||| ReportFormat.WithTracking + Adapter.VisitsClear() Assert.True(Instance.I.callerId () |> Option.isNone) @@ -1158,7 +1179,13 @@ module AltCoverTests = with :? 'a -> g () - let trywithrelease<'a when 'a :> exn> f = trywith f Instance.I.mutex.ReleaseMutex + let failsaferelease () = + try + Instance.I.mutex.ReleaseMutex() + with :? ApplicationException -> + () + + let trywithrelease<'a when 'a :> exn> f = trywith f failsaferelease [] let CanTryWith () = @@ -1173,11 +1200,15 @@ module AltCoverTests = Assert.That(flag, Is.True) + trywithrelease (fun () -> + InvalidOperationException() |> raise) + Instance.I.mutex.WaitOne(1000) |> ignore trywithrelease (fun () -> InvalidOperationException() |> raise) + [] let PauseLeavesExpectedTraces () = getMyMethodName "=>" @@ -1280,6 +1311,7 @@ module AltCoverTests = getMyMethodName "<=" + [] let ResumeLeavesExpectedTraces () = getMyMethodName "=>" @@ -1386,6 +1418,7 @@ module AltCoverTests = getMyMethodName "<=" + [] let FlushLeavesExpectedTraces () = getMyMethodName "=>" @@ -1644,12 +1677,7 @@ module AltCoverTests = visits.["f6e3edb3-fb20-44b3-817d-f69d1a22fc2f"] <- payload - Adapter.doFlush ( - visits, - AltCover.Recorder.ReportFormat.NCover, - reportFile, - outputFile - ) + Adapter.doFlush (visits, ReportFormat.NCover, reportFile, outputFile) |> ignore use worker' = @@ -1733,12 +1761,7 @@ module AltCoverTests = visits.["f6e3edb3-fb20-44b3-817d-f69d1a22fc2f"] <- payload - Adapter.doFlush ( - visits, - AltCover.Recorder.ReportFormat.NCover, - reportFile, - outputFile - ) + Adapter.doFlush (visits, ReportFormat.NCover, reportFile, outputFile) |> ignore use worker' = @@ -1815,7 +1838,7 @@ module AltCoverTests = Adapter.doFlush ( visits, - AltCover.Recorder.ReportFormat.NCover, + ReportFormat.NCover ||| ReportFormat.Zipped, reportFile, outputFile ) @@ -1848,6 +1871,7 @@ module AltCoverTests = Console.SetOut saved Directory.SetCurrentDirectory(here) AltCoverCoreTests.maybeIOException (fun () -> Directory.Delete(unique)) +#endif [] let ZipFlushLeavesExpectedTracesWhenBroken () = @@ -1905,7 +1929,7 @@ module AltCoverTests = Adapter.doFlush ( visits, - AltCover.Recorder.ReportFormat.NCover, + ReportFormat.NCover ||| ReportFormat.Zipped, reportFile, outputFile ) @@ -1976,7 +2000,12 @@ module AltCoverTests = visits.["f6e3edb3-fb20-44b3-817d-f69d1a22fc2f"] <- payload - Adapter.doFlush (visits, AltCover.Recorder.ReportFormat.NCover, reportFile, null) + Adapter.doFlush ( + visits, + ReportFormat.NCover ||| ReportFormat.Zipped, + reportFile, + null + ) |> ignore Assert.That(reportFile |> File.Exists |> not) @@ -1988,11 +2017,14 @@ module AltCoverTests = Directory.SetCurrentDirectory(here) AltCoverCoreTests.maybeIOException (fun () -> Directory.Delete(unique)) +#if !NET20 + [] let ZipFlushLeavesExpectedTraces () = getMyMethodName "=>" lock Adapter.Lock (fun () -> Instance.I.isRunner <- false + Instance.CoverageFormat <- ReportFormat.NCover ||| ReportFormat.Zipped trywithrelease (fun () -> let saved = Console.Out @@ -2028,6 +2060,8 @@ module AltCoverTests = Assert.That(stream.Read(buffer, 0, size), Is.EqualTo size) do + AltCoverCoreTests.maybeDeleteFile (Instance.ReportFilePath + ".zip") + use archive = ZipFile.Open(Instance.ReportFilePath + ".zip", ZipArchiveMode.Create) @@ -2098,6 +2132,7 @@ module AltCoverTests = finally Instance.I.trace <- save AltCoverCoreTests.maybeDeleteFile Instance.ReportFilePath + AltCoverCoreTests.maybeDeleteFile (Instance.ReportFilePath + ".zip") Adapter.VisitsClear() Console.SetOut saved Directory.SetCurrentDirectory(here) @@ -2106,18 +2141,9 @@ module AltCoverTests = getMyMethodName "<=" #endif - // Dead simple sequential operation - // run only once in Framework mode to avoid contention [] - let MailboxFunctionsAsExpected () = + let ShouldCreateDummyAttribute () = let dummy = AltCover.Recorder.ExcludeFromCodeCoverageAttribute() - Assert.That(dummy, Is.Not.Null) - RealIdShouldIncrementCount() - PauseLeavesExpectedTraces() - ResumeLeavesExpectedTraces() -#if !NET20 - ZipFlushLeavesExpectedTraces() -#endif - FlushLeavesExpectedTraces() \ No newline at end of file + Assert.That(dummy, Is.Not.Null) \ No newline at end of file diff --git a/AltCover.Recorder/Base.fs b/AltCover.Recorder/Base.fs index 5aae9a34..1335a70b 100644 --- a/AltCover.Recorder/Base.fs +++ b/AltCover.Recorder/Base.fs @@ -11,12 +11,21 @@ open System.Globalization open System.IO open System.Xml +[] +[] +[] type internal ReportFormat = | NCover = 0 | OpenCover = 1 - | OpenCoverWithTracking = 2 - | NativeJson = 3 - | NativeJsonWithTracking = 4 + | NativeJson = 2 + | TrackMask = 63 + | WithTracking = 64 + | ZipMask = 127 + | Zipped = 128 #if !RUNNER open ICSharpCode.SharpZipLib.Zip @@ -174,8 +183,7 @@ module internal Counter = ("//module", "moduleId", "method", [ ("seqpnt", 0) ], "visitcount") let internal xmlByFormat format = - match format with - | ReportFormat.OpenCoverWithTracking + match format &&& ReportFormat.TrackMask with | ReportFormat.OpenCover -> openCoverXml | ReportFormat.NCover -> nCoverXml | _ -> @@ -301,8 +309,7 @@ module internal Counter = |> Seq.toList |> List.rev)) |> Seq.mapi (fun counter (pt, flag) -> - ((match format with - | ReportFormat.OpenCoverWithTracking + ((match format &&& ReportFormat.TrackMask with | ReportFormat.OpenCover -> "uspid" |> pt.GetAttribute @@ -417,8 +424,14 @@ module internal Counter = | _ -> addSingleVisit counts moduleId hitPointId context 1L -#endif + [] + let doFlushStream postProcess pointProcess own counts format coverageFile outputFile = + I.doFlush postProcess pointProcess own counts format coverageFile outputFile + +#else [] @@ -456,7 +469,6 @@ module internal Counter = I.doFlush postProcess pointProcess own counts format coverageFile outputFile -#if !RUNNER [] @@ -467,7 +479,10 @@ module internal Counter = "CA2000:DisposeObjectsBeforeLosingScope", Justification = "ald also 'target' is disposed")>] let internal doFlushFile postProcess pointProcess own counts format report output = - if File.Exists report then + let zipped = + int (format &&& ReportFormat.Zipped) <> 0 + + if not zipped then use coverageFile = new FileStream( report, diff --git a/AltCover.Recorder/Recorder.fs b/AltCover.Recorder/Recorder.fs index 782c3753..88d97573 100644 --- a/AltCover.Recorder/Recorder.fs +++ b/AltCover.Recorder/Recorder.fs @@ -430,10 +430,11 @@ module Instance = adder moduleId hitPointId context - let internal isTrackingRunner () = - (CoverageFormat = ReportFormat.OpenCoverWithTracking - || CoverageFormat = ReportFormat.NativeJsonWithTracking) - && isRunner + let internal isTracking () = + (int (CoverageFormat &&& ReportFormat.WithTracking) + <> 0) + + let internal isTrackingRunner () = isTracking () && isRunner let internal granularity () = Timer let internal clock () = DateTime.UtcNow.Ticks @@ -499,10 +500,7 @@ module Instance = let Visit moduleId hitPointId = if I.recording then I.visitSelection - (if - (CoverageFormat = ReportFormat.OpenCoverWithTracking - || CoverageFormat = ReportFormat.NativeJsonWithTracking) - then + (if I.isTracking () then I.payloadSelector I.isTrackingRunner else Null) diff --git a/AltCover.RecorderModern.Tests/AltCover.RecorderModern.Tests.fsproj b/AltCover.RecorderModern.Tests/AltCover.RecorderModern.Tests.fsproj new file mode 100644 index 00000000..4e360438 --- /dev/null +++ b/AltCover.RecorderModern.Tests/AltCover.RecorderModern.Tests.fsproj @@ -0,0 +1,55 @@ + + + + net472 + false + AltCover.RecorderModern.Tests + false + NU1702, 3559 + NU1702 + --keyfile:$(InfrastructureKey) + RECORDERMODERN + + + + TRACE;DEBUG;ALTCOVER_TEST;$(ExtraDefines) + + + TRACE;RELEASE;ALTCOVER_TEST;$(ExtraDefines) + + + + + + + AssemblyVersion.fs + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + contentfiles + + + + + + + + + \ No newline at end of file diff --git a/AltCover.RecorderModern/AltCover.RecorderModern.fsproj b/AltCover.RecorderModern/AltCover.RecorderModern.fsproj new file mode 100644 index 00000000..a5819e90 --- /dev/null +++ b/AltCover.RecorderModern/AltCover.RecorderModern.fsproj @@ -0,0 +1,56 @@ + + + + net472 + AltCover.RecorderModern + AltCover.Recorder + false + false + false + RECORDER + + + + TRACE;$(DefineConstants) + --keyfile:$(InfrastructureKey) + + + + TRACE;DEBUG;CODE_ANALYSIS;$(DefineConstants) + --keyfile:$(InfrastructureKey) + + + + + + + + + + + + + + + AltCover.Recorder.Strings.resources + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + contentfiles + + + + + + ..\ThirdParty\ziplib.net20\ICSharpCode.SharpZipLib.dll + + + + \ No newline at end of file diff --git a/AltCover.Tests/Expecto.fs b/AltCover.Tests/Expecto.fs index 42f052b7..e0881028 100644 --- a/AltCover.Tests/Expecto.fs +++ b/AltCover.Tests/Expecto.fs @@ -38,10 +38,14 @@ module ExpectoTestManifest = "Runner.RepeatVisitsShouldIncrementTotal" Tests.AltCoverRunnerTests.KnownModuleWithPayloadMakesExpectedChangeInOpenCover, "Runner.KnownModuleWithPayloadMakesExpectedChangeInOpenCover" - Tests.AltCoverRunnerTests.FlushLeavesExpectedTraces, - "Runner.FlushLeavesExpectedTraces" - Tests.AltCoverRunnerTests.FlushLeavesExpectedTracesWhenDiverted, - "Runner.FlushLeavesExpectedTracesWhenDiverted" + Tests.AltCoverRunnerTests.DivertedWriteLeavesExpectedTraces, + "Runner.DivertedWriteLeavesExpectedTraces" + Tests.AltCoverRunnerTests.DivertedWriteJsonLeavesExpectedTraces, + "Runner.DivertedWriteJsonLeavesExpectedTraces" + Tests.AltCoverRunnerTests.DivertedZipWriteLeavesExpectedTraces, + "Runner.DivertedZipWriteLeavesExpectedTraces" + Tests.AltCoverRunnerTests.DivertedZipWriteJsonLeavesExpectedTraces, + "Runner.DivertedZipWriteJsonLeavesExpectedTraces" Tests.AltCoverRunnerTests.NCoverShouldGeneratePlausibleJson, "Runner.NCoverShouldGeneratePlausibleJson" Tests.AltCoverRunnerTests.OpenCoverShouldGeneratePlausibleJson, diff --git a/AltCover.Tests/Runner.Tests.fs b/AltCover.Tests/Runner.Tests.fs index 7eb938a9..b31d616c 100644 --- a/AltCover.Tests/Runner.Tests.fs +++ b/AltCover.Tests/Runner.Tests.fs @@ -301,208 +301,6 @@ module AltCoverRunnerTests = Is.EquivalentTo [ "2"; "2" ] ) - [] - let FlushLeavesExpectedTraces () = - Runner.init () - let saved = Console.Out - let here = Directory.GetCurrentDirectory() - - let where = - Assembly.GetExecutingAssembly().Location - |> Path.GetDirectoryName - - let unique = - Path.Combine(where, Guid.NewGuid().ToString()) - - let reportFile = - Path.Combine(unique, "FlushLeavesExpectedTraces.xml") - - try - let visits = - new Dictionary>() - - use stdout = new StringWriter() - Console.SetOut stdout - Directory.CreateDirectory(unique) |> ignore - Directory.SetCurrentDirectory(unique) - - Counter.measureTime <- - DateTime.ParseExact("2017-12-29T16:33:40.9564026+00:00", "o", null) - - use stream = - Assembly - .GetExecutingAssembly() - .GetManifestResourceStream(resource) - - let size = int stream.Length - let buffer = Array.create size 0uy - Assert.That(stream.Read(buffer, 0, size), Is.EqualTo size) - - do - use worker = - new FileStream(reportFile, FileMode.CreateNew) - - worker.Write(buffer, 0, size) - () - - let payload = Dictionary() - - [ 0..9 ] - |> Seq.iter (fun i -> payload.[i] <- init (int64 (i + 1)) []) - - visits.["f6e3edb3-fb20-44b3-817d-f69d1a22fc2f"] <- payload - - do - use coverageFile = - new FileStream( - reportFile, - FileMode.Open, - FileAccess.ReadWrite, - FileShare.None, - 4096, - FileOptions.SequentialScan - ) - - Counter.doFlushStream - ignore - (fun _ _ -> ()) - true - visits - AltCover.ReportFormat.NCover - coverageFile - None - |> ignore - - use worker' = - new FileStream(reportFile, FileMode.Open) - - let after = XmlDocument() - after.Load worker' - - Assert.That( - after.SelectNodes("//seqpnt") - |> Seq.cast - |> Seq.map _.GetAttribute("visitcount"), - Is.EquivalentTo - [ "11" - "10" - "9" - "8" - "7" - "6" - "4" - "3" - "2" - "1" ] - ) - finally - maybeDeleteFile reportFile - Console.SetOut saved - Directory.SetCurrentDirectory(here) - maybeIOException (fun () -> Directory.Delete(unique)) - - [] - let FlushLeavesExpectedTracesWhenDiverted () = - Runner.init () - let saved = Console.Out - let here = Directory.GetCurrentDirectory() - - let where = - Assembly.GetExecutingAssembly().Location - |> Path.GetDirectoryName - - let unique = - Path.Combine(where, Guid.NewGuid().ToString()) - - let reportFile = - Path.Combine(unique, "FlushLeavesExpectedTraces.xml") - - let outputFile = - Path.Combine(unique, "FlushLeavesExpectedTracesWhenDiverted.xml") - - try - let visits = - new Dictionary>() - - use stdout = new StringWriter() - Console.SetOut stdout - Directory.CreateDirectory(unique) |> ignore - Directory.SetCurrentDirectory(unique) - - Counter.measureTime <- - DateTime.ParseExact("2017-12-29T16:33:40.9564026+00:00", "o", null) - - use stream = - Assembly - .GetExecutingAssembly() - .GetManifestResourceStream(resource) - - let size = int stream.Length - let buffer = Array.create size 0uy - Assert.That(stream.Read(buffer, 0, size), Is.EqualTo size) - - do - use worker = - new FileStream(reportFile, FileMode.CreateNew) - - worker.Write(buffer, 0, size) - () - - let payload = Dictionary() - - [ 0..9 ] - |> Seq.iter (fun i -> payload.[i] <- init (int64 (i + 1)) []) - - visits.["f6e3edb3-fb20-44b3-817d-f69d1a22fc2f"] <- payload - - use coverageFile = - new FileStream( - reportFile, - FileMode.Open, - FileAccess.ReadWrite, - FileShare.None, - 4096, - FileOptions.SequentialScan - ) - - Counter.doFlushStream - ignore - (fun _ _ -> ()) - true - visits - AltCover.ReportFormat.NCover - coverageFile - (Some outputFile) - |> ignore - - use worker' = - new FileStream(outputFile, FileMode.Open) - - let after = XmlDocument() - after.Load worker' - - Assert.That( - after.SelectNodes("//seqpnt") - |> Seq.cast - |> Seq.map _.GetAttribute("visitcount"), - Is.EquivalentTo - [ "11" - "10" - "9" - "8" - "7" - "6" - "4" - "3" - "2" - "1" ] - ) - finally - maybeDeleteFile reportFile - Console.SetOut saved - Directory.SetCurrentDirectory(here) - maybeIOException (fun () -> Directory.Delete(unique)) - //Json.fs [] let NCoverShouldGeneratePlausibleJson () = @@ -2802,10 +2600,10 @@ module AltCoverRunnerTests = junkworker.Write([||], 0, 0) () - Runner.J.doReport counts AltCover.ReportFormat.NCover junkfile None + Runner.J.doReport counts AltCover.ReportFormat.NCover junkfile None // WriteLeavesExpectedTraces |> ignore - let (c0, w0) = Zip.openUpdate junkfile + let (c0, w0) = Zip.openUpdate junkfile false try Assert.That(c0 |> isNull) @@ -2814,7 +2612,7 @@ module AltCoverRunnerTests = finally w0.Dispose() - Runner.J.doReport counts AltCover.ReportFormat.NCover reportFile None + Runner.J.doReport counts AltCover.ReportFormat.NCover reportFile None // WriteLeavesExpectedTraces |> ignore use worker' = @@ -2930,12 +2728,12 @@ module AltCoverRunnerTests = exits.Add(2, pv) counts.Add(Track.Exit, exits) - Runner.J.doReport counts AltCover.ReportFormat.NativeJson reportFile (Some junkFile) + Runner.J.doReport counts AltCover.ReportFormat.NativeJson reportFile None // WriteJsonLeavesExpectedTraces |> ignore let jsonText = use worker' = - new FileStream(junkFile, FileMode.Open) + new FileStream(reportFile, FileMode.Open) use reader = new StreamReader(worker') reader.ReadToEnd() @@ -2994,9 +2792,13 @@ module AltCoverRunnerTests = let reportFile = Path.Combine(unique, "FlushLeavesExpectedTraces.xml") + let reportZip = reportFile + ".zip" + let junkfile = (reportFile + "." + (Path.GetFileName unique)) + let junkfile1 = junkfile + ".1" + let junkfile2 = junkfile + ".xml" try @@ -3037,22 +2839,25 @@ module AltCoverRunnerTests = // degenerate case 1 Assert.That(junkfile |> File.Exists |> not) - let (c0, w0) = Zip.openUpdate junkfile + let (c0, w0) = Zip.openUpdate junkfile true try - Assert.That(c0 |> isNull) + Assert.That(c0.IsNotNull) Assert.That(w0, Is.InstanceOf()) Assert.That(w0.Length, Is.EqualTo 0L) finally w0.Dispose() + c0.Dispose() // degenerate case 1a let junkzip = junkfile + ".zip" - Assert.That(junkzip |> File.Exists |> not) + Assert.That(junkzip |> File.Exists) + + let junk1zip = junkfile1 + ".zip" do use archive = - ZipFile.Open(junkzip, ZipArchiveMode.Create) + ZipFile.Open(junk1zip, ZipArchiveMode.Create) let entry = Guid.NewGuid().ToString() |> archive.CreateEntry @@ -3061,7 +2866,7 @@ module AltCoverRunnerTests = sink.Write([| 0uy |], 0, 1) () - let (c0, w0) = Zip.openUpdate junkfile + let (c0, w0) = Zip.openUpdate junkfile1 true try Assert.That(c0.IsNotNull) @@ -3074,25 +2879,34 @@ module AltCoverRunnerTests = // degenerate case 2 Assert.That(junkfile2 |> File.Exists |> not) - Runner.J.doReport counts AltCover.ReportFormat.NCover junkfile2 None + Runner.J.doReport + counts + (ReportFormat.NCover ||| ReportFormat.Zipped) + junkfile2 + None // ZipWriteLeavesExpectedTraces |> ignore Assert.That(junkfile2 |> File.Exists |> not) - let (c1, w1) = Zip.openUpdate junkfile2 + let (c1, w1) = Zip.openUpdate junkfile2 true try - Assert.That(c1 |> isNull) + Assert.That(c1.IsNotNull) Assert.That(w1, Is.InstanceOf()) Assert.That(w1.Length, Is.EqualTo 0L) finally - w0.Dispose() + w1.Dispose() + c1.Dispose() Assert.That(junkfile2 |> File.Exists |> not) - Runner.J.doReport counts AltCover.ReportFormat.NCover reportFile None + Runner.J.doReport + counts + (ReportFormat.NCover ||| ReportFormat.Zipped) + reportFile + None // ZipWriteLeavesExpectedTraces |> ignore let (container, worker) = - Zip.openUpdate reportFile + Zip.openUpdate reportFile true use worker' = worker use container' = container @@ -3116,9 +2930,14 @@ module AltCoverRunnerTests = "1" ] ) finally + Assert.That(reportFile |> File.Exists |> not) Assert.That(junkfile |> File.Exists |> not) + Assert.That(junkfile1 |> File.Exists |> not) Assert.That(junkfile2 |> File.Exists |> not) - maybeDeleteFile reportFile + maybeDeleteFile reportZip + maybeDeleteFile (junkfile + ".zip") + maybeDeleteFile (junkfile1 + ".zip") + maybeDeleteFile (junkfile2 + ".zip") Console.SetOut saved Directory.SetCurrentDirectory(here) maybeIOException (fun () -> Directory.Delete(unique)) @@ -3139,6 +2958,8 @@ module AltCoverRunnerTests = let reportFile = Path.Combine(unique, "WriteJsonLeavesExpectedTraces.json") + let reportZip = reportFile + ".zip" + let junkfile = (reportFile + "." + (Path.GetFileName unique)) @@ -3207,20 +3028,25 @@ module AltCoverRunnerTests = // degenerate case 1 Assert.That(junkfile |> File.Exists |> not) - let (c0, w0) = Zip.openUpdate junkfile + let (c0, w0) = Zip.openUpdate junkfile true try - Assert.That(c0 |> isNull) + Assert.That(c0.IsNotNull) Assert.That(w0, Is.InstanceOf()) Assert.That(w0.Length, Is.EqualTo 0L) finally w0.Dispose() + c0.Dispose() - Runner.J.doReport counts AltCover.ReportFormat.NativeJson reportFile None + Runner.J.doReport + counts + (ReportFormat.NativeJson ||| ReportFormat.Zipped) + reportFile + None // ZipWriteJsonLeavesExpectedTraces |> ignore let (container, worker) = - Zip.openUpdate reportFile + Zip.openUpdate reportFile true use container' = container @@ -3260,19 +3086,20 @@ module AltCoverRunnerTests = Is.EqualTo expected ) finally + Assert.That(reportFile |> File.Exists |> not) Assert.That(junkfile |> File.Exists |> not) Assert.That(junkfile2 |> File.Exists |> not) - maybeDeleteFile reportFile + maybeDeleteFile reportZip + maybeDeleteFile (junkfile + ".zip") Console.SetOut saved Directory.SetCurrentDirectory(here) maybeIOException (fun () -> Directory.Delete(unique)) [] - let NullPayloadShouldReportNothing () = + let DivertedWriteLeavesExpectedTraces () = Runner.init () - - let counts = - Dictionary>() + let saved = Console.Out + let here = Directory.GetCurrentDirectory() let where = Assembly.GetExecutingAssembly().Location @@ -3281,70 +3108,663 @@ module AltCoverRunnerTests = let unique = Path.Combine(where, Guid.NewGuid().ToString()) - do - use s = File.Create(unique + ".0.acv") - s.Close() + let reportFile = + Path.Combine(unique, "FlushLeavesExpectedTraces.xml") - let r = - Runner.J.getMonitor counts unique List.length [] + let outputFile = + Path.Combine(unique, "DivertedFlushLeavesExpectedTraces.xml") - Assert.That(r, Is.EqualTo 0) - Assert.That(File.Exists(unique + ".acv")) - Assert.That(counts, Is.Empty) + let junkfile = + (reportFile + "." + (Path.GetFileName unique)) - [] - let ActivePayloadShouldReportAsExpected () = - Runner.init () + try + use stdout = new StringWriter() + Console.SetOut stdout + Directory.CreateDirectory(unique) |> ignore + Directory.SetCurrentDirectory(unique) - let counts = - Dictionary>() + Counter.measureTime <- + DateTime.ParseExact("2017-12-29T16:33:40.9564026+00:00", "o", null) - let where = - Assembly.GetExecutingAssembly().Location - |> Path.GetDirectoryName + use stream = + Assembly + .GetExecutingAssembly() + .GetManifestResourceStream(resource) - let unique = - Path.Combine(where, Guid.NewGuid().ToString()) + do + use worker = + new FileStream(reportFile, FileMode.CreateNew) - let r = - Runner.J.getMonitor - counts - unique - (fun l -> - use sink = - new DeflateStream( - File.OpenWrite(unique + ".0.acv"), - CompressionMode.Compress - ) + stream.CopyTo worker - use formatter = new BinaryWriter(sink) + let hits = List() - l - |> List.mapi (fun i x -> - formatter.Write x - formatter.Write i - formatter.Write 0uy - x) - |> List.length) - [ "a"; "b"; String.Empty; "c" ] + [ 0..9 ] + |> Seq.iter (fun i -> + for j = 1 to i + 1 do + hits.Add("f6e3edb3-fb20-44b3-817d-f69d1a22fc2f", i, Null) + ignore j) - Assert.That(r, Is.EqualTo 4) - Assert.That(File.Exists(unique + ".acv")) + let counts = + Dictionary>() - let expected = - Dictionary>() + hits + |> Seq.iter (fun (moduleId, hitPointId, hit) -> + if counts.ContainsKey moduleId |> not then + counts.Add(moduleId, Dictionary()) - let a = Dictionary() - a.Add(0, init 1L []) - let b = Dictionary() - b.Add(1, init 1L []) - let c = Dictionary() - c.Add(3, init 1L []) - expected.Add("a", a) - expected.Add("b", b) - expected.Add("c", c) + AltCover.Counter.addVisit counts moduleId hitPointId hit + |> ignore) - Assert.That(counts.Count, Is.EqualTo 3) + // degenerate case + Assert.That(junkfile |> File.Exists |> not) + + do + use junkworker = + new FileStream(junkfile, FileMode.CreateNew) + + junkworker.Write([||], 0, 0) + () + + Runner.J.doReport counts AltCover.ReportFormat.NCover junkfile None // DivertedWriteLeavesExpectedTraces + |> ignore + + let (c0, w0) = Zip.openUpdate junkfile false + + try + Assert.That(c0 |> isNull) + Assert.That(w0, Is.InstanceOf()) + Assert.That(w0.Length, Is.EqualTo 8L) + finally + w0.Dispose() + + Runner.J.doReport counts AltCover.ReportFormat.NCover reportFile (Some outputFile) // DivertedWriteLeavesExpectedTraces + |> ignore + + use worker2 = + new FileStream(outputFile, FileMode.Open) + + let after = XmlDocument() + after.Load worker2 + + Assert.That( + after.SelectNodes("//seqpnt") + |> Seq.cast + |> Seq.map _.GetAttribute("visitcount"), + Is.EquivalentTo + [ "11" + "10" + "9" + "8" + "7" + "6" + "4" + "3" + "2" + "1" ] + ) + finally + maybeDeleteFile outputFile + maybeDeleteFile reportFile + maybeDeleteFile junkfile + Console.SetOut saved + Directory.SetCurrentDirectory(here) + maybeIOException (fun () -> Directory.Delete(unique)) + + [] + let DivertedWriteJsonLeavesExpectedTraces () = + Runner.init () + let saved = Console.Out + let here = Directory.GetCurrentDirectory() + + let where = + Assembly.GetExecutingAssembly().Location + |> Path.GetDirectoryName + + let unique = + Path.Combine(where, Guid.NewGuid().ToString()) + + let reportFile = + Path.Combine(unique, "WriteJsonLeavesExpectedTraces.json") + + let outputFile = + Path.Combine(unique, "DivertedWriteJsonLeavesExpectedTraces.json") + + let junkFile = + (reportFile + "." + (Path.GetFileName unique)) + + try + use stdout = new StringWriter() + Console.SetOut stdout + Directory.CreateDirectory(unique) |> ignore + Directory.SetCurrentDirectory(unique) + + let nativeJson = + Assembly + .GetExecutingAssembly() + .GetManifestResourceNames() + |> Seq.find _.EndsWith("Sample4.native.json", StringComparison.Ordinal) + + use stream = + Assembly + .GetExecutingAssembly() + .GetManifestResourceStream(nativeJson) + + do + use worker = + new FileStream(reportFile, FileMode.CreateNew) + + stream.CopyTo worker + + let tracks t = + [| Null + Call 0 + Time t + Both { Time = t; Call = 0 } |] + + let t0 = tracks 0L + + let hits = List() + + [ 0..9 ] + |> Seq.iter (fun i -> + for j = 1 to i + 1 do + hits.Add("Sample4.dll", i ||| (Counter.branchFlag * (i % 2)), t0.[i % 4]) + ignore j) + + let counts = + Dictionary>() + + hits + |> Seq.iter (fun (moduleId, hitPointId, hit) -> + if counts.ContainsKey moduleId |> not then + counts.Add(moduleId, Dictionary()) + + AltCover.Counter.addVisit counts moduleId hitPointId hit + |> ignore) + + let entries = Dictionary() + let pv = PointVisit.Create() + pv.Track(Time 512L) + tracks (1L) |> Seq.iter pv.Track + entries.Add(1, pv) + counts.Add(Track.Entry, entries) + + let exits = Dictionary() + let pv = PointVisit.Create() + tracks (2L) |> Seq.iter pv.Track + pv.Track(Time 1024L) + exits.Add(1, pv) + exits.Add(2, pv) + counts.Add(Track.Exit, exits) + + Runner.J.doReport + counts + AltCover.ReportFormat.NativeJson + reportFile + (Some outputFile) // DivertedWriteJsonLeavesExpectedTraces + |> ignore + + let jsonText = + use worker' = + new FileStream(outputFile, FileMode.Open) + + use reader = new StreamReader(worker') + reader.ReadToEnd() + // saved.WriteLine jsonText // NOT printfn "%s" jsonText + + let visitedJson = + Assembly + .GetExecutingAssembly() + .GetManifestResourceNames() + |> Seq.find + _.EndsWith("Sample4.syntheticvisits.native.json", StringComparison.Ordinal) + + use stream = + Assembly + .GetExecutingAssembly() + .GetManifestResourceStream(visitedJson) + + use reader = new StreamReader(stream) + + let expected = + reader + .ReadToEnd() + .Replace('\r', '\u00FF') + .Replace('\n', '\u00FF') + .Replace("\u00FF\u00FF", "\u00FF") + .Trim([| '\u00FF' |]) + + Assert.That( + jsonText + .Replace('\r', '\u00FF') + .Replace('\n', '\u00FF') + .Replace("\u00FF\u00FF", "\u00FF") + .Trim([| '\u00FF' |]), + Is.EqualTo expected + ) + finally + maybeDeleteFile reportFile + maybeDeleteFile outputFile + maybeDeleteFile junkFile + Console.SetOut saved + Directory.SetCurrentDirectory(here) + maybeIOException (fun () -> Directory.Delete(unique)) + + [] + let DivertedZipWriteLeavesExpectedTraces () = + Runner.init () + let saved = Console.Out + let here = Directory.GetCurrentDirectory() + + let where = + Assembly.GetExecutingAssembly().Location + |> Path.GetDirectoryName + + let unique = + Path.Combine(where, Guid.NewGuid().ToString()) + + let reportFile = + Path.Combine(unique, "FlushLeavesExpectedTraces.xml") + + let reportZip = reportFile + ".zip" + + let outputFile = + Path.Combine(unique, "DivertedFlushLeavesExpectedTraces.xml") + + let outputZip = outputFile + ".zip" + + let junkfile = + (reportFile + "." + (Path.GetFileName unique)) + + let junkfile1 = junkfile + ".1" + + let junkfile2 = junkfile + ".xml" + + let junkfile3 = junkfile + ".3.xml" + + try + use stdout = new StringWriter() + Console.SetOut stdout + Directory.CreateDirectory(unique) |> ignore + Directory.SetCurrentDirectory(unique) + + Counter.measureTime <- + DateTime.ParseExact("2017-12-29T16:33:40.9564026+00:00", "o", null) + + use stream = + Assembly + .GetExecutingAssembly() + .GetManifestResourceStream(resource) + + let doc = XDocument.Load stream + Zip.save (fun s -> doc.Save s) reportFile true // fsharplint:disable-line + + let hits = List() + + [ 0..9 ] + |> Seq.iter (fun i -> + for j = 1 to i + 1 do + hits.Add("f6e3edb3-fb20-44b3-817d-f69d1a22fc2f", i, Null) + ignore j) + + let counts = + Dictionary>() + + hits + |> Seq.iter (fun (moduleId, hitPointId, hit) -> + if counts.ContainsKey moduleId |> not then + counts.Add(moduleId, Dictionary()) + + AltCover.Counter.addVisit counts moduleId hitPointId hit + |> ignore) + + // degenerate case 1 + Assert.That(junkfile |> File.Exists |> not) + let (c0, w0) = Zip.openUpdate junkfile true + + try + Assert.That(c0.IsNotNull) + Assert.That(w0, Is.InstanceOf()) + Assert.That(w0.Length, Is.EqualTo 0L) + finally + w0.Dispose() + c0.Dispose() + + // degenerate case 1a + let junkzip = junkfile1 + ".zip" + Assert.That(junkzip |> File.Exists |> not) + + do + use archive = + ZipFile.Open(junkzip, ZipArchiveMode.Create) + + let entry = + Guid.NewGuid().ToString() |> archive.CreateEntry + + use sink = entry.Open() + sink.Write([| 0uy |], 0, 1) + () + + let (c0, w0) = Zip.openUpdate junkfile1 true + + try + Assert.That(c0.IsNotNull) + Assert.That(w0, Is.InstanceOf()) + Assert.That(w0.Length, Is.EqualTo 0L) + finally + c0.Dispose() + w0.Dispose() + + // degenerate case 2 + Assert.That(junkfile2 |> File.Exists |> not) + + Runner.J.doReport + counts + (ReportFormat.NCover ||| ReportFormat.Zipped) + junkfile2 + (Some junkfile3) + |> ignore + + Assert.That(junkfile2 |> File.Exists |> not) + let (c1, w1) = Zip.openUpdate junkfile2 true + + try + Assert.That(c1.IsNotNull) + Assert.That(w1, Is.InstanceOf()) + Assert.That(w1.Length, Is.EqualTo 0L) + finally + w1.Dispose() + c1.Dispose() + + Assert.That(junkfile2 |> File.Exists |> not) + + Runner.J.doReport + counts + (ReportFormat.NCover ||| ReportFormat.Zipped) + reportFile + (Some outputFile) + |> ignore + + let (container, worker) = + Zip.openUpdate outputFile true + + use worker' = worker + use container' = container + let after = XmlDocument() + after.Load worker' + + Assert.That( + after.SelectNodes("//seqpnt") + |> Seq.cast + |> Seq.map _.GetAttribute("visitcount"), + Is.EquivalentTo + [ "11" + "10" + "9" + "8" + "7" + "6" + "4" + "3" + "2" + "1" ] + ) + finally + Assert.That(reportFile |> File.Exists |> not, "unexpected reportfile") + Assert.That(outputFile |> File.Exists |> not, "unexpected outputfile") + Assert.That(junkfile |> File.Exists |> not, "unexpected junkfile") + Assert.That(junkfile1 |> File.Exists |> not, "unexpected junkfile1") + Assert.That(junkfile2 |> File.Exists |> not, "unexpected junkfile2") + Assert.That(junkfile3 |> File.Exists |> not, "unexpected junkfile3") + maybeDeleteFile reportZip + maybeDeleteFile outputZip + maybeDeleteFile (junkfile + ".zip") + maybeDeleteFile (junkfile1 + ".zip") + maybeDeleteFile (junkfile2 + ".zip") + maybeDeleteFile (junkfile3 + ".zip") + Console.SetOut saved + Directory.SetCurrentDirectory(here) + maybeIOException (fun () -> Directory.Delete(unique)) + + [] + let DivertedZipWriteJsonLeavesExpectedTraces () = + Runner.init () + let saved = Console.Out + let here = Directory.GetCurrentDirectory() + + let where = + Assembly.GetExecutingAssembly().Location + |> Path.GetDirectoryName + + let unique = + Path.Combine(where, Guid.NewGuid().ToString()) + + let reportFile = + Path.Combine(unique, "WriteJsonLeavesExpectedTraces.json") + + let reportZip = reportFile + ".zip" + + let outputFile = + Path.Combine(unique, "DivertedWriteJsonLeavesExpectedTraces.json") + + let outputZip = outputFile + ".zip" + + let junkfile = + (reportFile + "." + (Path.GetFileName unique)) + + let junkfile2 = junkfile + ".json" + + try + use stdout = new StringWriter() + Console.SetOut stdout + Directory.CreateDirectory(unique) |> ignore + Directory.SetCurrentDirectory(unique) + + let nativeJson = + Assembly + .GetExecutingAssembly() + .GetManifestResourceNames() + |> Seq.find _.EndsWith("Sample4.native.json", StringComparison.Ordinal) + + use stream = + Assembly + .GetExecutingAssembly() + .GetManifestResourceStream(nativeJson) + + Zip.save (stream.CopyTo) reportFile true // fsharplint:disable-line + + let tracks t = + [| Null + Call 0 + Time t + Both { Time = t; Call = 0 } |] + + let t0 = tracks 0L + + let hits = List() + + [ 0..9 ] + |> Seq.iter (fun i -> + for j = 1 to i + 1 do + hits.Add("Sample4.dll", i ||| (Counter.branchFlag * (i % 2)), t0.[i % 4]) + ignore j) + + let counts = + Dictionary>() + + hits + |> Seq.iter (fun (moduleId, hitPointId, hit) -> + if counts.ContainsKey moduleId |> not then + counts.Add(moduleId, Dictionary()) + + AltCover.Counter.addVisit counts moduleId hitPointId hit + |> ignore) + + let entries = Dictionary() + let pv = PointVisit.Create() + pv.Track(Time 512L) + tracks (1L) |> Seq.iter pv.Track + entries.Add(1, pv) + entries.Add(2, pv) + counts.Add(Track.Entry, entries) + + let exits = Dictionary() + let pv = PointVisit.Create() + tracks (2L) |> Seq.iter pv.Track + pv.Track(Time 1024L) + exits.Add(1, pv) + counts.Add(Track.Exit, exits) + + // degenerate case 1 + Assert.That(junkfile |> File.Exists |> not) + let (c0, w0) = Zip.openUpdate junkfile true + + try + Assert.That(c0.IsNotNull) + Assert.That(w0, Is.InstanceOf()) + Assert.That(w0.Length, Is.EqualTo 0L) + finally + w0.Dispose() + c0.Dispose() + + Runner.J.doReport + counts + (ReportFormat.NativeJson ||| ReportFormat.Zipped) + reportFile + (Some outputFile) // DivertedZipWriteJsonLeavesExpectedTraces + |> ignore + + let (container, worker) = + Zip.openUpdate outputFile true + + use container' = container + + let jsonText = + use worker' = worker + use reader = new StreamReader(worker') + reader.ReadToEnd() + + let visitedJson = + Assembly + .GetExecutingAssembly() + .GetManifestResourceNames() + |> Seq.find + _.EndsWith("Sample4.syntheticvisits.native.json", StringComparison.Ordinal) + + use stream = + Assembly + .GetExecutingAssembly() + .GetManifestResourceStream(visitedJson) + + use reader = new StreamReader(stream) + + let expected = + reader + .ReadToEnd() + .Replace('\r', '\u00FF') + .Replace('\n', '\u00FF') + .Replace("\u00FF\u00FF", "\u00FF") + .Trim([| '\u00FF' |]) + + Assert.That( + jsonText + .Replace('\r', '\u00FF') + .Replace('\n', '\u00FF') + .Replace("\u00FF\u00FF", "\u00FF") + .Trim([| '\u00FF' |]), + Is.EqualTo expected + ) + finally + Assert.That(reportFile |> File.Exists |> not, "unexpected report file") + Assert.That(outputFile |> File.Exists |> not, "unexpected outputfile") + Assert.That(junkfile |> File.Exists |> not, "unexpected junk file") + Assert.That(junkfile2 |> File.Exists |> not, "unexpected junk2 file") + maybeDeleteFile reportZip + maybeDeleteFile outputZip + maybeDeleteFile (junkfile + ".zip") + Console.SetOut saved + Directory.SetCurrentDirectory(here) + maybeIOException (fun () -> Directory.Delete(unique)) + + [] + let NullPayloadShouldReportNothing () = + Runner.init () + + let counts = + Dictionary>() + + let where = + Assembly.GetExecutingAssembly().Location + |> Path.GetDirectoryName + + let unique = + Path.Combine(where, Guid.NewGuid().ToString()) + + do + use s = File.Create(unique + ".0.acv") + s.Close() + + let r = + Runner.J.getMonitor counts unique List.length [] + + Assert.That(r, Is.EqualTo 0) + Assert.That(File.Exists(unique + ".acv")) + Assert.That(counts, Is.Empty) + + [] + let ActivePayloadShouldReportAsExpected () = + Runner.init () + + let counts = + Dictionary>() + + let where = + Assembly.GetExecutingAssembly().Location + |> Path.GetDirectoryName + + let unique = + Path.Combine(where, Guid.NewGuid().ToString()) + + let r = + Runner.J.getMonitor + counts + unique + (fun l -> + use sink = + new DeflateStream( + File.OpenWrite(unique + ".0.acv"), + CompressionMode.Compress + ) + + use formatter = new BinaryWriter(sink) + + l + |> List.mapi (fun i x -> + formatter.Write x + formatter.Write i + formatter.Write 0uy + x) + |> List.length) + [ "a"; "b"; String.Empty; "c" ] + + Assert.That(r, Is.EqualTo 4) + Assert.That(File.Exists(unique + ".acv")) + + let expected = + Dictionary>() + + let a = Dictionary() + a.Add(0, init 1L []) + let b = Dictionary() + b.Add(1, init 1L []) + let c = Dictionary() + c.Add(3, init 1L []) + expected.Add("a", a) + expected.Add("b", b) + expected.Add("c", c) + + Assert.That(counts.Count, Is.EqualTo 3) Assert.That(counts.["a"].Count, Is.EqualTo 1) Assert.That(counts.["b"].Count, Is.EqualTo 1) Assert.That(counts.["c"].Count, Is.EqualTo 1) @@ -3407,8 +3827,10 @@ module AltCoverRunnerTests = Assert.That(r, Is.EqualTo 0) Assert.That(File.Exists(unique + ".acv") |> not) + use stream = new MemoryStream() + let doc = - DocumentType.LoadReport ReportFormat.OpenCover (unique + ".acv") + DocumentType.LoadReportStream ReportFormat.OpenCover stream Assert.That(doc, Is.EqualTo DocumentType.Unknown) Assert.That(counts, Is.Empty) diff --git a/AltCover.Tests/Tests.fs b/AltCover.Tests/Tests.fs index cb6a4af3..343c31ae 100644 --- a/AltCover.Tests/Tests.fs +++ b/AltCover.Tests/Tests.fs @@ -3754,7 +3754,10 @@ module AltCoverTests = Assert.That( CoverageParameters.reportFormat (), - Is.EqualTo ReportFormat.NativeJsonWithTracking + Is.EqualTo( + ReportFormat.NativeJson + ||| ReportFormat.WithTracking + ) ) let result = makeJson document @@ -5477,7 +5480,10 @@ module AltCoverTests = Assert.That( CoverageParameters.reportFormat (), - Is.EqualTo ReportFormat.OpenCoverWithTracking + Is.EqualTo( + ReportFormat.OpenCover + ||| ReportFormat.WithTracking + ) ) "Program" @@ -5624,7 +5630,10 @@ module AltCoverTests = Assert.That( CoverageParameters.reportFormat (), - Is.EqualTo ReportFormat.OpenCoverWithTracking + Is.EqualTo( + ReportFormat.OpenCover + ||| ReportFormat.WithTracking + ) ) "Main" diff --git a/AltCover.Tests/Tests2.fs b/AltCover.Tests/Tests2.fs index 651af837..6f3dc66e 100644 --- a/AltCover.Tests/Tests2.fs +++ b/AltCover.Tests/Tests2.fs @@ -596,6 +596,8 @@ module AltCoverTests2 = [] let ShouldGetTrackingStyleIfSet () = + Main.init () + let save2 = CoverageParameters.theReportFormat @@ -608,7 +610,10 @@ module AltCoverTests2 = Assert.That( CoverageParameters.reportFormat (), - Is.EqualTo AltCover.ReportFormat.OpenCoverWithTracking + Is.EqualTo( + AltCover.ReportFormat.OpenCover + ||| ReportFormat.WithTracking + ) ) CoverageParameters.theInterval <- None @@ -616,7 +621,10 @@ module AltCoverTests2 = Assert.That( CoverageParameters.reportFormat (), - Is.EqualTo AltCover.ReportFormat.OpenCoverWithTracking + Is.EqualTo( + AltCover.ReportFormat.OpenCover + ||| ReportFormat.WithTracking + ) ) CoverageParameters.trackingNames.Clear() @@ -716,6 +724,7 @@ module AltCoverTests2 = CoverageParameters.theReportFormat <- Some AltCover.ReportFormat.OpenCover CoverageParameters.theInterval <- Some 1234567890 CoverageParameters.single <- true + CoverageParameters.zipReport.Value <- true Assert.That( CoverageParameters.sampling (), @@ -794,7 +803,9 @@ module AltCoverTests2 = Assert.That( report2, - AltCover.ReportFormat.OpenCoverWithTracking + (AltCover.ReportFormat.OpenCover + ||| ReportFormat.WithTracking + ||| ReportFormat.Zipped) |> int |> Is.EqualTo, "wrong tracking format" diff --git a/AltCover.Tests/XTests.fs b/AltCover.Tests/XTests.fs index 9aa20b0b..2bc99d5d 100644 --- a/AltCover.Tests/XTests.fs +++ b/AltCover.Tests/XTests.fs @@ -220,6 +220,8 @@ module AltCoverXTests = [] let TypeSafeCollectOptionsCanBeValidated () = + CommandLine.error <- [] + let here = Assembly.GetExecutingAssembly().Location @@ -311,6 +313,8 @@ module AltCoverXTests = [] let TypeSafeCollectOptionsCanBeValidatedWithErrors () = + CommandLine.error <- [] + let subject = TypeSafe.CollectOptions.Create() @@ -347,6 +351,8 @@ module AltCoverXTests = [] let TypeSafeCollectOptionsCanBePositivelyValidatedWithErrors () = + CommandLine.error <- [] + let test = { TypeSafe.CollectOptions.Create() with RecorderDirectory = @@ -909,7 +915,11 @@ module AltCoverXTests = test <@ String.Join("; ", actualFiles) = String.Join("; ", theFiles) @> let recordedJson = - match DocumentType.LoadReport CoverageParameters.theReportFormat.Value report with + use stream = File.OpenRead report + + match + DocumentType.LoadReportStream CoverageParameters.theReportFormat.Value stream + with | JSON j -> j test <@ recordedJson.Keys |> Seq.toList = [ "Sample4.dll" ] @> @@ -1155,7 +1165,9 @@ module AltCoverXTests = XDocument.Load(new StringReader(expectedText)) let recordedXml = - match DocumentType.LoadReport ReportFormat.OpenCover report with + use stream = File.OpenRead report + + match DocumentType.LoadReportStream ReportFormat.OpenCover stream with | XML x -> x RecursiveValidate (recordedXml.Elements()) (expectedXml.Elements()) 0 true @@ -1504,6 +1516,15 @@ module AltCoverXTests = = test' <@ report = codedreport @> "should be default coverage file" test <@ output = Some alternate @> + + use stream = + Assembly + .GetExecutingAssembly() + .GetManifestResourceStream("AltCover.Tests.GenuineNCover158.Xml") + + use fs = File.Create(alternate) + stream.CopyTo fs + test <@ hits |> Seq.isEmpty @> TimeSpan.Zero diff --git a/AltCover.sln b/AltCover.sln index 222e0b98..3d25d661 100644 --- a/AltCover.sln +++ b/AltCover.sln @@ -35,6 +35,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build Items", "Build Items", "{97367D43-64EF-48E1-B6B4-D951C783E6E1}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .fantomasignore = .fantomasignore Build\actions.fs = Build\actions.fs Build\AddStrongName.fsx = Build\AddStrongName.fsx Build\AltCover.nuspec = Build\AltCover.nuspec @@ -135,6 +136,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cecil", "Cecil", "{F56DE54A ThirdParty\cecil\Mono.Cecil.Rocks.pdb = ThirdParty\cecil\Mono.Cecil.Rocks.pdb EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample0", "Samples\Sample0\Sample0.csproj", "{19CE63CE-3622-409A-974D-3EA01388317E}" +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "AltCover.RecorderModern", "AltCover.RecorderModern\AltCover.RecorderModern.fsproj", "{219B0218-3289-44DD-9C22-6C14A3D7B5A7}" +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "AltCover.RecorderModern.Tests", "AltCover.RecorderModern.Tests\AltCover.RecorderModern.Tests.fsproj", "{4B905582-3295-463B-A645-7FA6D0844035}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -326,9 +333,23 @@ Global {B42E9FBA-BDE8-4B15-B7E7-DDF237CA7881}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B42E9FBA-BDE8-4B15-B7E7-DDF237CA7881}.Debug|Any CPU.Build.0 = Debug|Any CPU {B42E9FBA-BDE8-4B15-B7E7-DDF237CA7881}.Debug|x86.ActiveCfg = Debug|Any CPU - {B42E9FBA-BDE8-4B15-B7E7-DDF237CA7881}.Debug|x86.Build.0 = Debug|Any CPU {B42E9FBA-BDE8-4B15-B7E7-DDF237CA7881}.Release|Any CPU.ActiveCfg = Release|Any CPU {B42E9FBA-BDE8-4B15-B7E7-DDF237CA7881}.Release|x86.ActiveCfg = Release|Any CPU + {19CE63CE-3622-409A-974D-3EA01388317E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19CE63CE-3622-409A-974D-3EA01388317E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19CE63CE-3622-409A-974D-3EA01388317E}.Debug|x86.ActiveCfg = Debug|Any CPU + {19CE63CE-3622-409A-974D-3EA01388317E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19CE63CE-3622-409A-974D-3EA01388317E}.Release|x86.ActiveCfg = Release|Any CPU + {219B0218-3289-44DD-9C22-6C14A3D7B5A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {219B0218-3289-44DD-9C22-6C14A3D7B5A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {219B0218-3289-44DD-9C22-6C14A3D7B5A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {219B0218-3289-44DD-9C22-6C14A3D7B5A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {219B0218-3289-44DD-9C22-6C14A3D7B5A7}.Release|x86.ActiveCfg = Release|Any CPU + {4B905582-3295-463B-A645-7FA6D0844035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B905582-3295-463B-A645-7FA6D0844035}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B905582-3295-463B-A645-7FA6D0844035}.Debug|x86.ActiveCfg = Debug|Any CPU + {4B905582-3295-463B-A645-7FA6D0844035}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B905582-3295-463B-A645-7FA6D0844035}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -355,6 +376,7 @@ Global {10FB27A2-9C4D-474D-A3B9-1C554038AA7E} = {2837CE07-B91F-4B8A-89B5-E7BE39A8F340} {B42E9FBA-BDE8-4B15-B7E7-DDF237CA7881} = {2837CE07-B91F-4B8A-89B5-E7BE39A8F340} {F56DE54A-ABD6-42F7-B61E-7BAFCCF2D787} = {97367D43-64EF-48E1-B6B4-D951C783E6E1} + {19CE63CE-3622-409A-974D-3EA01388317E} = {2837CE07-B91F-4B8A-89B5-E7BE39A8F340} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C31111CF-68A2-403F-9B06-9625FCBD48E3} diff --git a/Build/actions.fs b/Build/actions.fs index 1f20ad33..b62592e3 100644 --- a/Build/actions.fs +++ b/Build/actions.fs @@ -112,6 +112,7 @@ open System.Runtime.CompilerServices [] [] [] +[] [] #else [] diff --git a/Build/targets.fs b/Build/targets.fs index e2ac8ef4..4e75be90 100644 --- a/Build/targets.fs +++ b/Build/targets.fs @@ -2376,7 +2376,7 @@ module Targets = let prep = AltCover.PrepareOptions.TypeSafe( { TypeSafe.PrepareOptions.Create() with - Report = TypeSafe.FilePath altReport + Report = TypeSafe.FilePath(testDirectory @@ coverageReport) OutputDirectories = TypeSafe.DirectoryPaths [| TypeSafe.DirectoryPath outputDirectory |] StrongNameKey = TypeSafe.FilePath signingKey @@ -2409,6 +2409,7 @@ module Targets = AltCover.CollectOptions.TypeSafe { TypeSafe.CollectOptions.Create() with Executable = TypeSafe.FilePath nunitConsole + OutputFile = TypeSafe.FilePath altReport RecorderDirectory = TypeSafe.DirectoryPath(testDirectory @@ outputDirectory) CommandLine = @@ -3333,6 +3334,93 @@ module Targets = Actions.ValidateFSharpTypesCoverage simpleReport2) + let BasicCSharp0 = + (fun () -> + let samplePath = + "_Binaries/Sample0/Debug+AnyCPU/net20" + + let binaryPath = + "_Binaries/AltCover/Release+AnyCPU/net472" + + let reportSigil = "BasicCSharp0" + + printfn "Instrument and run a simple executable" + Directory.ensure "./_Reports" + + let simpleReport = + (Path.getFullName "./_Reports") + @@ (reportSigil + ".xml") + + let binRoot = Path.getFullName binaryPath + let sampleRoot = Path.getFullName samplePath + + let instrumented = + "__Instrumented." + reportSigil + + let framework = + Fake.DotNet.ToolType.CreateFullFramework() + + let prep = + AltCover.PrepareOptions.Primitive + { Primitive.PrepareOptions.Create() with + // Verbosity = System.Diagnostics.TraceLevel.Verbose + TypeFilter = [ """System\.""" ] + Report = simpleReport + OutputDirectories = [| "./" + instrumented |] + ReportFormat = "NCover" + InPlace = false + Save = false } + |> AltCoverCommand.Prepare + + let parameters = + { AltCoverCommand.Options.Create prep with + ToolPath = binRoot @@ "AltCover.exe" + ToolType = framework + WorkingDirectory = sampleRoot } + + AltCoverCommand.run parameters + System.Threading.Thread.Sleep(1000) + + Actions.Run + (sampleRoot @@ (instrumented + "/Sample0.exe"), (sampleRoot @@ instrumented), []) + "Instrumented .exe failed" + + System.Threading.Thread.Sleep(1000) + + use coverageFile = // fsharplint:disable-next-line RedundantNewKeyword + new FileStream( + simpleReport, + FileMode.Open, + FileAccess.Read, + FileShare.None, + 4096, + FileOptions.SequentialScan + ) + + use reader = XmlReader.Create(coverageFile) + + let coverageDocument = + XDocument.Load(reader) + + let recorded = + coverageDocument.Descendants(XName.Get("seqpnt")) + |> Seq.toList + + Assert.That( + List.length recorded, + Is.EqualTo 1, + "unexpected points in " + reportSigil + ) + + let ones = + recorded + |> Seq.filter (fun x -> x.Attribute(XName.Get("visitcount")).Value = "1") + |> Seq.map _.Attribute(XName.Get("line")).Value + |> Seq.sort + |> Seq.toList + + Assert.That(List.length ones, Is.EqualTo 1, "unexpected visits in " + reportSigil)) + let BasicCSharp = (fun () -> Actions.SimpleInstrumentingRun @@ -4729,7 +4817,7 @@ module Targets = let binRoot = Path.getFullName binaryPath let sampleRoot = - Path.getFullName "_Binaries/Sample1/Debug+AnyCPU/net20" + Path.getFullName "_Mono/Sample1" let instrumented = "__Instrumented." + reportSigil @@ -8283,7 +8371,7 @@ module Targets = _Target "FSAsyncTests" FSAsyncTests _Target "FSharpTypesDotNetRunner" FSharpTypesDotNetRunner _Target "FSharpTypesDotNetCollecter" FSharpTypesDotNetCollecter - _Target "BasicCSharp" BasicCSharp + _Target "BasicCSharp" BasicCSharp0 // virus false +ve // BasicCSharp _Target "BasicCSharpMono" BasicCSharpMono _Target "BasicCSharpUnderMono" BasicCSharpUnderMono _Target "BasicCSharpMonoUnderMono" BasicCSharpMonoUnderMono diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 045a62fa..1552a523 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -3,7 +3,12 @@ A. Start with the Quick Start guide : https://github.com/SteveGilham/altcover/wiki/QuickStart-Guide and read the FAQ : https://github.com/SteveGilham/altcover/wiki/FAQ -# (Habu series release 28) +# (Habu series release 29) +* [BUGFIX] Fix summary data for `--outputFile` option +* [BUGFIX] Fix interaction of `--zipFile` prepare option and `--outputFile` collect option +* [BUGFIX] Issue #220 - improve dependency resolution to the GAC + +# 8.8.21 (Habu series release 28) * [BREAKING; BUGFIX] Issue #206 : Update to net6+ for `dotnet test` integration and respect the `$(IsTestProject)` setting from the `Microsoft.NET.Test.Sdk` package. * Simplify the use of the AltCover MSBuild tasks via the associated package-level `.targets` file by not even including the `VSTest` integration unless both `'$(AltCover)' == 'true' AND '$(IsTestProject)' == 'true'`. * Mitigate instances of `System.IO.IOException: The process cannot access the file '[coverage report]' because it is being used by another process.` diff --git a/Samples/Sample0/Program.cs b/Samples/Sample0/Program.cs new file mode 100644 index 00000000..194e1bfc --- /dev/null +++ b/Samples/Sample0/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +System.Console.WriteLine("Hello, World!"); \ No newline at end of file diff --git a/Samples/Sample0/Sample0.csproj b/Samples/Sample0/Sample0.csproj new file mode 100644 index 00000000..0a89fbea --- /dev/null +++ b/Samples/Sample0/Sample0.csproj @@ -0,0 +1,9 @@ + + + + Exe + net20 + 9.0 + + +