FSharp FsUnit No test is available in Make sure that test discoverer & executors are registered and platform & framework - f#

OK so a google search reveals 100s if not thousands of links but they are very old threads talking about NUnit and Nuget. I am not using these these tools.
I am writing a F# project with FsUnit and dotnet command line (no visual studio business).
I created a F# project like
dotnet new console -lang "F#" -o CustomerProject
dotnet add package FsUnit
dotnet add package FsUnit.Xunit
dotnet add package Microsoft.TestPlatform
dotnet add package Microsoft.TestPlatform.TestHost
So my .fsproj file looks like
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Customer.fs" />
<Compile Include="CustomerTests.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FsUnit" Version="5.0.5" />
<PackageReference Include="FsUnit.Xunit" Version="5.0.5" />
<PackageReference Include="Microsoft.TestPlatform" Version="17.3.2" />
<PackageReference Include="Microsoft.TestPlatform.TestHost" Version="17.3.2" />
</ItemGroup>
</Project>
In my project directory I have 2 files
Customer.fs
namespace MyProject.Customer
open System
type Customer = {
Id : int
isVip: bool
Credit: decimal
}
module Domain =
let getPurchases customer =
let purchases = if customer.Id % 2 = 0 then 120M else 80M in
(customer, purchases)
let tryPromoteToVip purchases =
let (customer, amount) = purchases in
if amount > 100M then {customer with isVip = true} else customer
let increaseCreditIfVip customer =
let increase = if customer.isVip then 100M else 50M in
{customer with Credit = customer.Credit + increase}
let upgradeCustomer customer =
customer
|> getPurchases
|> tryPromoteToVip
|> increaseCreditIfVip
CustomerTests.fs
module CustomerTests
open Xunit
open FsUnit
open MyProject.Customer
open MyProject.Customer.Domain
module ``When upgrading customer`` =
let customerVIP = {Id = 1; isVip = true; Credit = 0.0M}
let customerSTD = {Id = 2; isVip = false; Credit = 100.0M}
[<Fact>]
let ``should give VIP cstomer more credit`` () =
let expected = {customerVIP with Credit = customerVIP.Credit + 100.0M }
let actual = upgradeCustomer customerVIP
actual |> should equal expected
yet when I run dotnet test from the command line I get an error
No test is available in /Users/user/code/fsharp/CustomerProject/bin/Debug/net6.0/CustomerProject.dll. Make sure that test discoverer & executors are registered and platform & framework version settings are appropriate and try again.
Additionally, path to test adapters can be specified using /TestAdapterPath command. Example /TestAdapterPath:<pathToCustomAdapters>.

According to this SO answer, you need to add an xUnit runner to your project as well. And according to the xUnit documentation, you have to add xunit.runner.visualstudio, even if you just want to use dotnet test:
dotnet add package xunit.runner.visualstudio
(There's also an xunit.runner.console package on NuGet, but I think it's just for old .NET Framework projects.)

Related

Let ParseAndCheckFileInProject recognize symbols from Nuget dependecies

When using FSharp.Compiler.SourceCodeServices.FSharpChecker.ParseAndCheckFileInProject from FSharp Compiler Service for whole-project analysis, how can NuGet dependencies be included for symbolic resolution?
In the project under analysis, some project file (*.fsproj) contains some <PackageReference> element, e.g. <PackageReference Include="NodaTime" Version="3.0.3" />, and some source file contains an open statement open NodaTime and somewhere some symbol DateInterval refering to NodaTime.DateInterval. For me, the FSharp Compiler Service seems to fail to resolve DateInterval to NodaTime.DateInterval in that source file.
What I am currently doing is, summarized:
let checker = FSharpChecker.Create()
let options: FSharpProjectOptions = { ... }
checker.ParseAndCheckFileInProject (...)
Given that I use ParseAndCheckFileInProject, what is necessary for checker and/or options to consider symbols coming from NuGet dependencies?
I think you have to extract the FSharpProjectOptions from the .fsproj project file. For old-style .NET Framework projects, there used to be a method called ProjectCracker.GetProjectOptionsFromProjectFile that would do this, but it has been replaced with Dotnet.ProjInfo, which also supports .NET Core. See this SO question for details.

Manipulate an XML file with FAKE

Question: How do I set a specific attribute in an XML file with FAKE?
I want to build a Windows Phone 8.1 project with the help of FAKE. The version tag is a parameter of the build script. The tag is already baked into the assemply info:
let version = getBuildParamOrDefault "version" "0.0.0.1"
Target "AssemblyInfo" (fun _ ->
CreateCSharpAssemblyInfo "./src/SharedAssemblyInfo.cs"
[
Attribute.Product product
Attribute.Version version
Attribute.FileVersion version
Attribute.Metadata ("githash", commitHash)
]
)
There is another file in a Windows Phone 8.1 project that contains version information: Package.appxmanifest.
<?xml version="1.0" encoding="utf-8"?>
<Package ...>
<Identity Name="..." Publisher="..." Version="1.0.0.0" />
...
</Package>
I want to change the value of the Version attribute of the Identity tag. It should contain the version tag given as build parameter. I want to do it in a separate target with the name "AppxManifest" and it should execute after manipulating the assembly info file but before the MSBuild build is executed:
Target "AppxManifest" (fun _ ->
???
)
"Clean"
==> "RestorePackages"
==> "AssemblyInfo"
==> "AppxManifest"
==> "Build"
I have found an XMLHelper in the documentation of FAKE (http://fsharp.github.io/FAKE/apidocs/fake-xmlhelper.html) and it looks like that this thing can do the job. But there are no examples. I could not figure out how to do it.
You can use the XMLHelper library to manipulate XML files. The function XmlPoke can be used to change an element in an XML file by referenceing it with an XPath expression. The Package.appxmanifest file contains namespaces, therefore you have to take the sister function XmlPokeNS which can handle namespaces properly.
The last difficulty is the default namespace in Package.appxmanifest. You have to specify a prefix which you associate with the default namespace. In your XPath expression this prefix is used to reference the XML elements.
There is no special prefix string, you can choose whatever you want. In this example I choose the prefix "df":
open Fake.XMLHelper
Target "PackageAppxmanifest" <| fun _ ->
let file = "<path to Package.appxmanifest>"
let ns = [ "df", "http://schemas.microsoft.com/appx/2010/manifest" ]
let xpath = #"df:Package/df:Identity/#Version"
XmlPokeNS file ns xpath version

Setting Solution Filename on OutDir

I have a file set which is a scan for all of the *.sln files in my source tree. There is currently 4 folders
With the masterParam I am passing in the GenerateProjectSpecificOutputFolder and setting the outdir
What I am looking to do is for every sln that is being built with the MSBuild to append the solution name as part of the outDir.
I am using F# FAKE
Below is from the Target Build.
let masterParam = [
("Configuration",buildMode);
("GenerateProjectSpecificOutputFolder","true");
("OutDir",("drop/<solutionName>");
("BuildInParallel", "false");
]
// compile all projects below src/app/
MSBuild buildDir "Build" masterParam appReferences
|> Log "Build-Output: "
I don't think you can do this in any "automatic" way, but since FAKE lets you write arbitrary F# code, you can iterate over the projects and run MSBuild on individual projects.
Something like this should do the trick (you might need to open System.IO):
for proj in appReferences do
let name = Path.GetFileNameWithoutExtension(proj)
let masterParam = [
("Configuration",buildMode);
("GenerateProjectSpecificOutputFolder","true");
("OutDir",("drop/" + name);
("BuildInParallel", "false");
]
MSBuild buildDir "Build" masterParam [proj]
|> Log "Build-Output: "

Using NLog with F# Interactive in Visual Studio - Need documentation

I have a need to capture the input and output of F# functions when using F# Interactive. I am able to get NLog to work just fine when the program is run under Visual Studio using F5 or Ctrl-F5. Also the same methods that contain statements to output to the log work just fine and are called when invoked via F# Interactive; just nothing in the log file.
I also tried the following with F# Interactive to setup references to NLog and still nothing in the log when run from F# Interactive.
#I #"..\packages\NLog.2.0.0.2000\lib\net40"
#r #"NLog.dll"
And I even found this which led me to try each of these
NLog.Config.SimpleConfigurator.ConfigureForConsoleLogging()
NLog.Config.SimpleConfigurator.ConfigureForFileLogging(<full file name>)
and still nothing in the log file.
Anyone know if Nlog can be used with F# Interactive?
If so, how is it done?
EDIT
I was able to get NLog to work with fsi.exe when run as a stand alone. So now the problem appears to be getting NLog to find the config file because NLog cannot find the config file starting from the location of fsi.exe for Visual Studio. Looking at using NLog.dll.nlog in the NLog.dll directory.
The Problem
The problem with using NLog from F# Interactive is that NLog thinks that the Temp directory is where to find NLog.config and never succeeds. The way around this is to programmatically locate NLog.config for NLog.
Things to know to solve this problem:
When you run F# Interactive from within Visual Studio, it sets the current working directory to a temp file.
> System.Environment.CurrentDirectory;;
val it : string = "C:\Users\Eric\AppData\Local\Temp"
NLog logging requires three components:
a. reference to NLog.dll.
b. configuration file.
c. calls to a logger method from code.
NLog can be configured in many ways, both programmatically and using config files.
AppData is a hidden folder. Guess what that means when using Windows Explorer.
To get the location of the application with F# Interactive within Visual Studio you need __SOURCE_DIRECTORY__. See F# Spec 3.11 Identifier Replacements
NLog.conf can use a full file path. Obvious but necessary.
NLog file targets have an autoFlush option.
NLog can be installed into a Visual Studio project using NuGet.
Most of the info here comes from NLog Wiki.
Instead of jumping right into the F# Interactive solution, the following progression will be used because a DLL will need to be created to setup and hold the functions for use with NLog from F# Interactive.
Create a solution with three projects and install NLog.
Solution Name: NLogExample
Project 1 - Library, Name: Log - holds extension functions that call NLog
Project 2 - Library, Name: MyLibrary - used to generate a demo DLL that uses Log functions.
Project 3 - Console Application, Name: Main - used to generate a demo EXE that uses Log functions.
a. Manually create NLog.config
b. Access NLog.config from as a running project
c. Log a message to the file
a. Programmatically create a configuration
b. Create a configuration for a running project and log a message to the file
Create a configuration and log a message to the file using F# Interactive
1. Create a solution with three projects and install NLog
Using Visual Studio create the three projects.
Install NLog for all three projects.
2.a. Manually create NLog.config
Note: For these examples to work when __SOURCE_DIRECTORY__;; is run from F# Interactive it should report a directory that is part of the project and NOT the Temp directory.
Note: All the paths in this answer are relative to the solution directory.
When you see <Solution directory> substitute in your actual solution directory.
Path: <Solution director>\NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
throwExceptions="true">
<targets>
<target xsi:type="File"
name="file"
fileName="<Solution directory>\log.txt"
autoFlush="true"
/>
</targets>
<rules>
<logger name="*"
minlevel="Trace"
writeTo="file"
/>
</rules>
</nlog>
Note: Remember to change <Solution directory> to an actual path and set autoFlush="true"
Note: Adding NLog.config to the solution makes it easier to view/modify the file.
2.b. Access NLog.config from as a running project
In Log.Library1.fs
namespace Log
module MyLog =
let configureNLog () =
let projectPath = __SOURCE_DIRECTORY__
let soulutionPath = projectPath + "\.."
let configPath = soulutionPath + #"\NLog.config"
let xmlConfig = new NLog.Config.XmlLoggingConfiguration(configPath)
NLog.LogManager.Configuration <- xmlConfig
let NLogConfigToString () =
let targets = NLog.LogManager.Configuration.AllTargets
let out = ""
let out = Seq.fold (fun out target -> out + (sprintf "%A\n" target)) out targets
let rules = NLog.LogManager.Configuration.LoggingRules
let out = Seq.fold (fun out rule -> out + (sprintf "%A\n" rule)) out rules
out
let printNLogConfig () =
Printf.printfn "%s" (NLogConfigToString ())
and for the Log project add a reference to System.XML
In Main.Program.fs
open Log
[<EntryPoint>]
let main argv =
MyLog.configureNLog ()
MyLog.printNLogConfig ()
0 // return an integer exit code
and for the Main project add a reference to the Log project and set the Main project as the startup project.
When run this should output to the console:
File Target[file]
logNamePattern: (:All) levels: [ Trace Debug Info Warn Error Fatal ] appendTo: [ file ]
2.c. Log a message to the file
In Log.Library1.fs
namespace Log
open NLog
module MyLog =
let configureNLog () =
let projectPath = __SOURCE_DIRECTORY__
let soulutionPath = projectPath + "\.."
let configPath = soulutionPath + #"\NLog.config"
let xmlConfig = new NLog.Config.XmlLoggingConfiguration(configPath)
NLog.LogManager.Configuration <- xmlConfig
let NLogConfigToString () =
let targets = NLog.LogManager.Configuration.AllTargets
let out = ""
let out = Seq.fold (fun out target -> out + (sprintf "%A\n" target)) out targets
let rules = NLog.LogManager.Configuration.LoggingRules
let out = Seq.fold (fun out rule -> out + (sprintf "%A\n" rule)) out rules
out
let printNLogConfig () =
Printf.printfn "%s" (NLogConfigToString ())
let evalTracer = LogManager.GetLogger("file")
In Main.Program.fs
open Log
open Library1
[<EntryPoint>]
let main argv =
MyLog.configureNLog ()
MyLog.printNLogConfig ()
// Add as many of these as needed
MyLog.evalTracer.Trace("In Main #1.")
MyFunctions.test001 ()
0 // return an integer exit code
and for the Main project add a reference to the MyLibrary project.
In MyLibrary.Library1.fs
namespace Library1
open Log
module MyFunctions =
let test001 () =
MyLog.evalTracer.Trace("In Library #1.")
and for the MyLibrary project add a reference to the Log project.
When run the log file log.txt should contain something similar to:
2016-03-28 11:03:52.4963|TRACE|file|In Main #1.
2016-03-28 11:03:52.5263|TRACE|file|In Library #1
3.a. Programmatically create a configuration
If a NLog.config file exist delete it to verify that the code created a new configuration but did not create a file.
To set the configuration programmatically using F# you need to know:
This FileName string is a layout which may include instances of layout renderers. This lets you use a single target to write to multiple files.
SimpleLayout - Represents a string with embedded placeholders that can render contextual information.
To Log.Library1.fs add
let configureNLogPrgramatically () =
let config = new NLog.Config.LoggingConfiguration()
let fileTarget = new NLog.Targets.FileTarget()
let projectPath = __SOURCE_DIRECTORY__
let soulutionPath = projectPath + "\.."
let filePath = soulutionPath + #"\log.txt"
let layout = new NLog.Layouts.SimpleLayout(filePath)
fileTarget.Name <- "file"
fileTarget.FileName <- layout
fileTarget.AutoFlush <- true
config.AddTarget("file", fileTarget)
let rule1 = new NLog.Config.LoggingRule("*",NLog.LogLevel.Trace,fileTarget)
config.LoggingRules.Add(rule1)
NLog.LogManager.Configuration <- config
3.b. Create a configuration for a running project and log a message to the file
In Main.Program.fs
open Log
open Library1
[<EntryPoint>]
let main argv =
MyLog.configureNLogPrgramatically ()
MyLog.printNLogConfig ()
// Add as many of these as needed
MyLog.evalTracer.Trace("In Main #1.")
MyFunctions.test001 ()
0 // return an integer exit code
When run the log file log.txt should contain something similar to:
2016-03-28 11:16:07.2901|TRACE|file|In Main #1.
2016-03-28 11:16:07.3181|TRACE|file|In Library #1.
and note that a NLog.config file was NOT created.
4. Create a configuration and log a message to the file using F# Interactive
In MyLibrary.Script.fsx
// print out __SOURCE_DIRECTORY__ to make sure we are not using the Temp directory
printfn __SOURCE_DIRECTORY__
#I __SOURCE_DIRECTORY__
// Inform F# Interactive where to find functions in Log module
#I "../Log/bin/Debug/"
#r "Log.dll"
open Log
// Functions in Log module can now be run.
MyLog.configureNLogPrgramatically ()
MyLog.printNLogConfig ()
// Inform F# Interactive where to find functions in MyLibrary module
#I "../MyLibrary/bin/Debug/"
#r "MyLibrary.dll"
open Library1
// Functions in MyLibrary module can now be run.
MyFunctions.test001 ()
When the script is executed with F# Interactive
Microsoft (R) F# Interactive version 14.0.23413.0
Copyright (c) Microsoft Corporation. All Rights Reserved.
For help type #help;;
>
<Solution directory>\MyLibrary
val it : unit = ()
--> Added <Solution directory>\MyLibrary' to library include path
--> Added <Solution directory>\MyLibrary\../Log/bin/Debug/' to library include path
--> Referenced <Solution directory>\MyLibrary\../Log/bin/Debug/Log.dll'
File Target[file]
logNamePattern: (:All) levels: [ Trace Debug Info Warn Error Fatal ] appendTo: [ file ]
--> Added <Solution directory>\MyLibrary\../MyLibrary/bin/Debug/' to library include path
--> Referenced <Solution directory>\MyLibrary\../MyLibrary/bin/Debug/MyLibrary.dll'
val it : unit = ()
>
The log file log.txt should contain something similar to:
2016-03-28 11:42:41.5417|TRACE|file|In Library #1.
Also, this will log while you still have an active F# Interactive session, so you can peek at the log between executing commands.

Set Wix property to TFS build location

I have been trying to find an answer to my question and could not find it; hence I will put the solution here. I hope it is helpful to others.
Problem:
I want my Wix project to build in TFS 2010 build process. As part of this, I want the source files location for my Wix to point to the build location of the TFS. For example, I want:
<File Id="ABC" KeyPath="yes" source="C:\Builds\1\MyBuild\assembly.dll" />
to be:
<File Id="ABC" KeyPath="yes" source="$(var.TFSLOCATION)\assembly.dll" />
The 'TFSLOCATION' is a wix property that needs to be populated with the location of TFS build. This needs to happen during the build process, where the build location path is passed to the Wix project.
Solution:
I read the following article:
http://www.ageektrapped.com/blog/setting-properties-for-wix-in-msbuild/
So this is what I did to my Wix project file (wixproj):
In order to set wix property from TFS MSBuild process the wix project file needs two changes:
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<ProductVersion>3.5</ProductVersion>
<SourceLocation Condition="'$(SourceLocation)' == '' ">UNKNOWN</SourceLocation>
<ProjectGuid>{cae7e273-2de5-4a60-9c4f-9da5f094caf5}</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>N4S.MSO.BAM.Installer</OutputName>
<OutputType>Package</OutputType>
<WixTargetsPath Condition=" '$(WixTargetsPath)' == '' AND '$(MSBuildExtensionsPath32)' != '' ">$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath>
<WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath>
<SccProjectName>SAK</SccProjectName>
<SccProvider>SAK</SccProvider>
<SccAuxPath>SAK</SccAuxPath>
<SccLocalPath>SAK</SccLocalPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>LOCATION=$(SourceLocation)</DefineConstants>
</PropertyGroup>
In the above xml, please note the following two lines:
<SourceLocation Condition="'$(SourceLocation)' == '' ">UNKNOWN</SourceLocation>
<DefineConstants>LOCATION=$(SourceLocation)</DefineConstants>
The first line specifies a property 'SourceLocation' and sets it to a default 'UNKNOWN' value, if is is not set. The second line defines a constant called 'LOCATION' in the 'Release' configuration. The value of this constant is set to the value of 'SourceLocation' property.
Now, you need to make the following changes to your Product.wxs file (or whatever the name of your wxs file is).
Define a wix property first.
<?define TFSLOCATION="$(var.LOCATION)"?>
Now, update the File elements.
<File Id="ABC" KeyPath="yes" source="$(var.TFSLOCATION)\assembly.dll" />
TFS 2010 build template change
Open the TFS 2010 build template.
Look for a task 'Run MSBuild for Project'.
Open the properties for this task and go to 'CommandLineArguments' property.
Set the value for this property to:
String.Format("/p:SourceLocation={0}", BinariesDirectory)
Done
You now have a wix property populated from your TFS build process.
If you set a reference in your wixproj file to the project(s) you are building you can reference their target paths. So if you have two projects MyProject and MyProjectInstaller, set a reference in MyProjectInstaller to MyProject.
Now in the product.wxs file your File elements would look like this:
<File Id='EXE' Name='$(var.MyProject.TargetDir)\MyProject.exe' />
<File Id='DLL' Name='$(var.MyProject.TargetDir)\MyProject.dll' />
...
The benefit to this is that the target dir is correct regardless of whether you're building locally or on a build server.
Answering the question so it doesn't show up with no answers even though the answer is in the question.
I ended up using the same approach, but with a few important improvements.
1) I passed both the SourcesDirectory and the BinariesDirectory from TFS build process template to MSBuild as separate properties, so that I have access to both of them.
2) So that the resulting MSBuild properties are available to every task executed in the MSBuild project, I added them to $(CustomPropertiesForBuild) in the BeforeBuild target.
3) Rrather than adding the DefineContants element to the PropertyGroup, I added a CreateProperty to the BeforeBuild target.
2 and 3 were done for the following reason having to do with running multiple Wix project solutions in a single TFS build. If you define the constant as originally suggested, two things can happen.
First, if you run heat.exe in your WiX project as part of a build that has multiple WiX projects, an issue can occur where the DevEnv holds onto the process handles and the constant is not redefined on each run unless you clean the Output folder and release the file handle.
Second, if for any reason one of your Wix projects does not get built (the configuration does not specify to build it or the specified configuration is invalid for the project) then for some reason, the MSBuild property gets reset to null and thus the constant is redefined as null, so you lose the property. If on the other hand, you define the property by overriding the BeforeBuild target, everything works properly.
Note that you have to override the BeforeBuild property, not the BeforeEndToEndIteration property, for this to work correctly.

Resources