Skip to content

Implemented #634: Arg.AnyType can be used in place of a generic type parameter #715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bld/
[Bb]in/
[Oo]bj/
[Ll]og/
buildOutput

# VS Code directory
.vscode/
Expand Down
63 changes: 63 additions & 0 deletions build/ExtractDocs.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module ExtractDocs

open System
open System.IO
open System.Text.RegularExpressions

let LiquidTagRegex = @"```(?<tag>\w+)" + // Tag start with argument. e.g. "```csharp"
@"(?<contents>(?s:.*?))" + // Tag contents
@"```?" // Tag end
let TypeOrTestDeclRegex = @"(\[Test\]|(public |private |protected )?(class |interface )\w+\s*\{)"

type LiquidTag = LiquidTag of name : string * contents : string
type CodeBlock = Declaration of string | Snippet of string

let tags s : LiquidTag seq =
Regex.Matches(s, LiquidTagRegex)
|> Seq.cast<Match>
|> Seq.map (fun m -> LiquidTag (m.Groups.[1].Value, m.Groups.[2].Value))

let toCodeBlock (LiquidTag (name, c)) =
let isTypeOrTestDecl s = Regex.IsMatch(s, TypeOrTestDeclRegex)
match name with
| "csharp" -> if isTypeOrTestDecl c then Some (Declaration c) else Some (Snippet c)
| "requiredcode" -> Some (Declaration c)
| _ -> None

let toCodeBlocks : string -> CodeBlock seq =
Seq.choose toCodeBlock << tags

let toFixture name content =
let escapeName (s:string) = Regex.Replace(s, "\W", "_")
sprintf """
using System;
using NUnit.Framework;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using NSubstitute.Extensions;
using NSubstitute.Compatibility;

namespace NSubstitute.Samples {
public class Tests_%s {
%s
}
}""" (escapeName name) content

let toTest = sprintf """[Test] public void Test_%d() {
%s
}"""

let appendCodeBlock (code, testNum) (cb:CodeBlock) =
match cb with
| Declaration d -> (code + Environment.NewLine + d, testNum)
| Snippet s ->
let test = toTest testNum s
(code + Environment.NewLine + test, testNum + 1)

let strToFixture fixtureName s : string =
s
|> toCodeBlocks
|> Seq.fold appendCodeBlock ("", 0)
|> fst
|> toFixture fixtureName
62 changes: 0 additions & 62 deletions build/ExtractDocs.fsx

This file was deleted.

8 changes: 1 addition & 7 deletions build/build.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,4 @@ cls
set encoding=utf-8

SET SCRIPT_DIR=%~dp0
SET TOOL_PATH=%SCRIPT_DIR%\.fake

IF NOT EXIST "%TOOL_PATH%\fake.exe" (
dotnet tool install fake-cli --tool-path %TOOL_PATH%
)

"%TOOL_PATH%\fake.exe" --silent run %SCRIPT_DIR%\build.fsx %*
dotnet run --project "%SCRIPT_DIR%/build.fsproj" -- %*
249 changes: 249 additions & 0 deletions build/build.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
open System
open System.Diagnostics
open System.IO
open System.Text.RegularExpressions

open Fake.Core
open Fake.Core.TargetOperators
open Fake.DotNet
open Fake.IO
open Fake.IO.Globbing.Operators
open Fake.IO.FileSystemOperators
open Fake.Tools

open ExtractDocs

let target = Target.create
let description = Target.description

module FileReaderWriter =
let Read file = File.ReadAllText(file)
let Write file text = File.WriteAllText(file, text)
let TransformFile file target (f : string -> string) =
Read file
|> f
|> Write target

module ExamplesToCode =
open FileReaderWriter

let ConvertFile (file: string) targetDir =
let fileName = Path.GetFileNameWithoutExtension(file)
let target = targetDir @@ fileName + ".cs"
Trace.log <| sprintf "Converting %s to %s" file target
TransformFile file target (ExtractDocs.strToFixture fileName)

let Convert paths targetDir =
let paths = paths |> Seq.toList
for p in paths do
Trace.trace <| sprintf "Convert from %s to %s" p targetDir
let files = !! "*.markdown" ++ "*.html" ++ "*.md" |> GlobbingPattern.setBaseDir p
for file in files do
ConvertFile file targetDir

type BuildVersion = { assembly: string; file: string; info: string; package: string }
let getVersion () =
// The --first-parent flag is needed to make our walk linear from current commit and top.
// This way also merge commit is counted as "1".
let desc = Git.CommandHelper.runSimpleGitCommand "" "describe --tags --long --abbrev=40 --first-parent --match=v*"
let result = Regex.Match(desc,
@"^v(?<maj>\d+)\.(?<min>\d+)\.(?<rev>\d+)(?<pre>-\w+\d*)?-(?<num>\d+)-g(?<sha>[a-z0-9]+)$",
RegexOptions.IgnoreCase)
.Groups
let getMatch (name:string) = result.[name].Value

let (major, minor, revision, preReleaseSuffix, commitsNum, commitSha) =
(getMatch "maj" |> int, getMatch "min" |> int, getMatch "rev" |> int, getMatch "pre", getMatch "num" |> int, getMatch "sha")

// Assembly version should contain major and minor only, as no breaking changes are expected in bug fix releases.
let assemblyVersion = sprintf "%d.%d.0.0" major minor
let fileVersion = sprintf "%d.%d.%d.%d" major minor revision commitsNum

// If number of commits since last tag is greater than zero, we append another identifier with number of commits.
// The produced version is larger than the last tag version.
// If we are on a tag, we use version without modification.
// Examples of output: 3.50.2.1, 3.50.2.215, 3.50.1-rc1.3, 3.50.1-rc3.35
let packageVersion = match commitsNum with
| 0 -> sprintf "%d.%d.%d%s" major minor revision preReleaseSuffix
| _ -> sprintf "%d.%d.%d%s.%d" major minor revision preReleaseSuffix commitsNum

let infoVersion = match commitsNum with
| 0 -> packageVersion
| _ -> sprintf "%s-%s" packageVersion commitSha

{ assembly = assemblyVersion; file = fileVersion; info = infoVersion; package = packageVersion }

let root = __SOURCE_DIRECTORY__ </> ".." |> Path.getFullName

let configuration = Environment.environVarOrDefault "configuration" "Debug"
let version = getVersion ()

let additionalArgs = [
"AssemblyVersion", version.assembly
"FileVersion", version.file
"InformationalVersion", version.info
"PackageVersion", version.package
]

let output = root </> "bin" </> configuration
let solution = (root </> "NSubstitute.sln")

let initTargets() =
Target.create "Default" ignore
Target.create "All" ignore

Target.description("Clean compilation artifacts and remove output bin directory")
Target.create "Clean" (fun _ ->
DotNet.exec (fun p -> { p with WorkingDirectory = root }) "clean"
(sprintf "--configuration %s --verbosity minimal" configuration)
|> ignore
Shell.cleanDirs [ output ]
)

Target.description("Restore dependencies")
Target.create "Restore" (fun _ ->
DotNet.restore (fun p -> p) solution
)

Target.description("Compile all projects")
Target.create "Build" (fun _ ->
DotNet.build (fun p ->
{ p with Configuration = DotNet.BuildConfiguration.fromString configuration
MSBuildParams = { p.MSBuildParams with Properties = additionalArgs }
}) solution
)

Target.description("Run tests")
Target.create "Test" (fun _ ->
DotNet.test (fun p ->
{ p with Configuration = DotNet.BuildConfiguration.fromString configuration
MSBuildParams = { p.MSBuildParams with Properties = additionalArgs }
}) (root </> "tests/NSubstitute.Acceptance.Specs/NSubstitute.Acceptance.Specs.csproj")
)

Target.description("Generate Nuget package")
Target.create "Package" (fun _ ->
DotNet.pack (fun p ->
{ p with Configuration = DotNet.BuildConfiguration.fromString configuration
MSBuildParams = { p.MSBuildParams with Properties = additionalArgs }
}) (root </> "src/NSubstitute/NSubstitute.csproj")
)

Target.description("Run all benchmarks. Must be run with configuration=Release.")
Target.create "Benchmarks" (fun _ ->
if configuration <> "Release" then
failwith "Benchmarks can only be run in Release mode. Please re-run the build in Release configuration."

let benchmarkCsproj = root </> "tests/NSubstitute.Benchmarks/NSubstitute.Benchmarks.csproj" |> Path.getFullName
let benchmarkToRun = Environment.environVarOrDefault "benchmark" "*" // Defaults to "*" (all)
[ "netcoreapp2.1" ]
|> List.iter (fun framework ->
Trace.traceImportant ("Benchmarking " + framework)
let work = output </> "benchmark-" + framework
Directory.ensure work
DotNet.exec (fun p -> { p with WorkingDirectory = work }) "run"
("--framework " + framework + " --project " + benchmarkCsproj + " -- " + benchmarkToRun)
|> ignore
)
)

Target.description("Extract, build and test code from documentation.")
Target.create "TestCodeFromDocs" <| fun _ ->
let outputCodePath = output </> "CodeFromDocs"
Directory.create outputCodePath
// generate samples from docs
ExamplesToCode.Convert [ root </> "docs/"; root </> "docs/help/_posts/"; root ] outputCodePath
// compile code samples
let csproj = """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\NSubstitute\NSubstitute.csproj" />
</ItemGroup>
</Project>
"""
let projPath = outputCodePath </> "Docs.csproj"
FileReaderWriter.Write projPath csproj
DotNet.restore (fun p -> p) projPath
DotNet.build (fun p -> p) projPath
DotNet.test (fun p -> p) projPath

let tryFindFileOnPath (file : string) : string option =
Environment.GetEnvironmentVariable("PATH").Split([| Path.PathSeparator |])
|> Seq.append ["."]
|> fun path -> ProcessUtils.tryFindFile path file

Target.description("Build documentation website. Requires Ruby, bundler and jekyll.")
Target.create "Documentation" <| fun _ ->
Trace.log "Building site..."
let exe = [ "bundle.bat"; "bundle" ]
|> Seq.map tryFindFileOnPath
|> Seq.collect (Option.toList)
|> Seq.tryFind (fun _ -> true)
|> function | Some x -> Trace.log ("using " + x); x
| None -> Trace.log ("count not find exe"); "bundle"

let workingDir = root </> "docs/"
let docOutputRelativeToWorkingDir = ".." </> output </> "nsubstitute.github.com"

// TODO migrate the following to FAKE API: CreateProcess.ofStartInfo(p)
// https://fake.build/apidocs/v5/fake-core-createprocess.html
// that doesn't work for some reason
let p = ProcessStartInfo(
UseShellExecute = false,
CreateNoWindow = true,
FileName = exe,
WorkingDirectory = workingDir,
Arguments = "exec jekyll build -d \"" + docOutputRelativeToWorkingDir + "\"")
let proc = Process.Start(p)
proc.WaitForExit()
let result = proc.ExitCode
if result = 0 then
"Site built in " + docOutputRelativeToWorkingDir |> Trace.log
else
"failed to build site" |> failwith

Target.description("List targets, similar to `rake -T`. For more details, run `--listTargets` instead.")
Target.create "-T" <| fun _ ->
printfn "Optional config options:"
printfn " configuration=Debug|Release"
printfn " benchmark=*|<benchmark name> (only for Benchmarks target in Release mode)"
printfn ""
Target.listAvailable()

"Clean" ?=> "Build" |> ignore
"Clean" ?=> "Test" |> ignore
"Clean" ?=> "Restore" |> ignore
"Clean" ?=> "Documentation" |> ignore
"Clean" ?=> "TestCodeFromDocs" |> ignore
"Clean" ?=> "Package" |> ignore
"Clean" ?=> "Default" |> ignore

"Build" <== [ "Restore" ]
"Test" <== [ "Build" ]
"Documentation" <== [ "TestCodeFromDocs" ]
"Benchmarks" <== [ "Build" ]
// For packaging, use a clean build and make sure all tests (inc. docs) pass.
"Package" <== [ "Clean"; "Build"; "Test"; "TestCodeFromDocs" ]

"Default" <== [ "Restore"; "Build"; "Test" ]
"All" <== [ "Clean"; "Default"; "Documentation"; "Package" ]

[<EntryPoint>]
let main argv =
argv
|> Array.toList
|> Context.FakeExecutionContext.Create false "build.fsx"
|> Context.RuntimeContext.Fake
|> Context.setExecutionContext
initTargets()
Target.runOrDefaultWithArguments "Default"
0
Loading