Skip to content
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

WIP: Opentelemetry #497

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
6 changes: 4 additions & 2 deletions Expecto.Tests/Expecto.Tests.fsproj
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Expecto.Tests</AssemblyName>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Prelude.fs" />
<Compile Include="OpenTelemetry.fs" />
<Compile Include="Tests.fs" />
<Compile Include="SummaryTests.fs" />
<Compile Include="FocusedTests.fs" />
Expand All @@ -20,4 +22,4 @@
<ProjectReference Include="..\Expecto\Expecto.fsproj" />
</ItemGroup>
<Import Project="..\.paket\Paket.Restore.targets" />
</Project>
</Project>
17 changes: 15 additions & 2 deletions Expecto.Tests/Main.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@ module Main

open Expecto
open Expecto.Logging
open OpenTelemetry.Resources
open OpenTelemetry
open OpenTelemetry.Trace
open System.Threading
open System.Diagnostics
open System

let serviceName = "Expecto.Tests"

let logger = Log.create serviceName

let logger = Log.create "Expecto.Tests"

[<EntryPoint>]
let main args =

let test =
Impl.testFromThisAssembly()
|> Option.orDefault (TestList ([], Normal))
|> Test.shuffle "."
runTestsWithCLIArgs [NUnit_Summary "bin/Expecto.Tests.TestResults.xml"] args test
runTestsWithCLIArgs [NUnit_Summary "bin/Expecto.Tests.TestResults.xml";] args test



172 changes: 172 additions & 0 deletions Expecto.Tests/OpenTelemetry.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
namespace Expecto

module OpenTelemetry =
open System
open System.Diagnostics
open System.Collections.Generic
open System.Threading
open Impl


module internal Activity =
let inline isNotNull x = isNull x |> not

let inline setStatus (status : ActivityStatusCode) (span : Activity) =
if isNotNull span then
span.SetStatus(status) |> ignore

let inline setExn (e : exn) (span : Activity) =
if isNotNull span|> not then
let tags =
ActivityTagsCollection(
seq {
KeyValuePair("exception.type", box (e.GetType().Name))
KeyValuePair("exception.stacktrace", box (e.ToString()))
if not <| String.IsNullOrEmpty(e.Message) then
KeyValuePair("exception.message", box e.Message)
}
)

ActivityEvent("exception", tags = tags)
|> span.AddEvent
|> ignore

let inline setExnMarkFailed (e : exn) (span : Activity) =
if isNotNull span then
setExn e span
span |> setStatus ActivityStatusCode.Error

let setSourceLocation (sourceLoc : SourceLocation) (span : Activity) =
if isNotNull span && sourceLoc <> SourceLocation.empty then
span.SetTag("code.lineno", sourceLoc.lineNumber) |> ignore
span.SetTag("code.filepath", sourceLoc.sourcePath) |> ignore

let inline addOutcome (result : TestResult) (span : Activity) =
if isNotNull span then
span.SetTag("test.result.status", result.tag) |> ignore
span.SetTag("test.result.message", result) |> ignore

let inline start (span : Activity) =
if isNotNull span then
span.Start() |> ignore
span

let inline stop (span : Activity) =
if isNotNull span then
span.Stop() |> ignore

let inline setEndTimeNow (span : Activity) =
if isNotNull span then
span.SetEndTime(DateTime.UtcNow) |> ignore

let inline createActivity (name : string) (source : ActivitySource) =
match source with
| source when not(isNull source) -> source.CreateActivity(name, ActivityKind.Internal)
| _ -> null

open Activity
open System.Runtime.ExceptionServices
open System.IO

let inline internal reraiseAnywhere<'a> (e: exn) : 'a =
ExceptionDispatchInfo.Capture(e).Throw()
Unchecked.defaultof<'a>

module TestResult =
let ofException (e:Exception) : TestResult =
match e with
| :? AssertException as e ->
let msg =
"\n" + e.Message + "\n" +
(e.StackTrace.Split('\n')
|> Seq.skipWhile (fun l -> l.StartsWith(" at Expecto.Expect."))
|> Seq.truncate 5
|> String.concat "\n")
Failed msg

| :? FailedException as e ->
Failed ("\n"+e.Message)
| :? IgnoreException as e ->
Ignored e.Message
| :? AggregateException as e when e.InnerExceptions.Count = 1 ->
if e.InnerException :? IgnoreException then
Ignored e.InnerException.Message
else
Error e.InnerException
| e ->
Error e


let addExceptionOutcomeToSpan (span: Activity) (e: Exception) =
let testResult = TestResult.ofException e

addOutcome testResult span
match testResult with
| Ignored _ ->
setExn e span
| _ ->
setExnMarkFailed e span

let wrapCodeWithSpan (span: Activity) (test: TestCode) =
let inline handleSuccess span =
setEndTimeNow span
addOutcome Passed span
setStatus ActivityStatusCode.Ok span
let inline handleFailure span e =
setEndTimeNow span
addExceptionOutcomeToSpan span e
reraiseAnywhere e

match test with
| Sync test ->
TestCode.Sync (fun () ->
use span = start span
try
test ()
handleSuccess span
with
| e ->
handleFailure span e
)

| Async test ->
TestCode.Async (async {
use span = start span
try
do! test
handleSuccess span
with
| e ->
handleFailure span e
})
| AsyncFsCheck (testConfig, stressConfig, test) ->
TestCode.AsyncFsCheck (testConfig, stressConfig, fun fsCheckConfig -> async {
use span = start span
try
do! test fsCheckConfig
handleSuccess span
with
| e ->
handleFailure span e
})
| SyncWithCancel test->
TestCode.SyncWithCancel (fun ct ->
use span = start span
try
test ct
handleSuccess span
with
| e ->
handleFailure span e
)

let addOpenTelemetry_SpanPerTest (config: ExpectoConfig) (activitySource: ActivitySource) (rootTest: Test) : Test =
rootTest
|> Test.toTestCodeList
|> List.map (fun test ->
let span = activitySource |> createActivity (config.joinWith.format test.name)
span |> setSourceLocation (config.locate test.test)
{test with test = wrapCodeWithSpan span test.test}
)
|> Test.fromFlatTests config.joinWith.asString

46 changes: 46 additions & 0 deletions Expecto.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ open Expecto
open Expecto.Impl
open Expecto.Logging
open System.Globalization
open OpenTelemetry.Resources
open OpenTelemetry.Trace
open System.Diagnostics
open OpenTelemetry

let serviceName = "Expecto.Tests"

let source = new ActivitySource(serviceName)

let resourceBuilder () =
ResourceBuilder
.CreateDefault()
.AddService(serviceName = serviceName)

let traceProvider () =
Sdk
.CreateTracerProviderBuilder()
.AddSource(serviceName)
.SetResourceBuilder(resourceBuilder ())
.AddOtlpExporter()
.Build()
do
let provider = traceProvider()
AppDomain.CurrentDomain.ProcessExit.Add(fun _ -> provider.Dispose())


module Dummy =

Expand Down Expand Up @@ -1380,6 +1405,8 @@ let asyncTests =
]

open System.Threading.Tasks
open OpenTelemetry
open System.Diagnostics

[<Tests>]
let taskTests =
Expand Down Expand Up @@ -1828,6 +1855,7 @@ let cancel =
)
]


[<Tests>]
let theory =
testList "theory testing" [
Expand Down Expand Up @@ -1855,3 +1883,21 @@ let theory =
}
]
]
|> addOpenTelemetry_SpanPerTest ExpectoConfig.defaultConfig source



[<Tests>]
let fixtures =
let rng = Random()
let tests = [
for i in 1..(Environment.ProcessorCount * 2) do
testCaseAsync (sprintf "test %d" i) <| async {
printfn "Running test %d" i
do! Async.Sleep(rng.Next(1, 5000))
printfn "Finished Running test %d" i
}
]

testList "MyTests" tests
|> addOpenTelemetry_SpanPerTest ExpectoConfig.defaultConfig source
5 changes: 4 additions & 1 deletion Expecto.Tests/paket.references
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
FsCheck
FsCheck
OpenTelemetry.Exporter.OpenTelemetryProtocol
YoloDev.Expecto.TestSdk
Microsoft.NET.Test.Sdk
1 change: 1 addition & 0 deletions Expecto/Expecto.Impl.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Expecto

open System
open System.Collections.Generic
open System.Diagnostics
open System.Reflection
open System.Threading
Expand Down
2 changes: 2 additions & 0 deletions Expecto/Expecto.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module Tests =
open Impl
open Helpers
open Expecto.Logging
open System.Diagnostics

let mutable private afterRunTestsList = []
let private afterRunTestsListLock = obj()
Expand Down Expand Up @@ -445,6 +446,7 @@ module Tests =
/// Specify test names join character.
| JoinWith of split: string


let options = [
"--sequenced", "Don't run the tests in parallel.", Args.none Sequenced
"--parallel", "Run all tests in parallel (default).", Args.none Parallel
Expand Down
3 changes: 3 additions & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ nuget Hopac ~> 0.4
nuget DiffPlex ~> 1.5
nuget Mono.Cecil ~> 0.11
nuget BenchmarkDotNet ~> 0.13.5
nuget OpenTelemetry.Exporter.OpenTelemetryProtocol
nuget YoloDev.Expecto.TestSdk
nuget Microsoft.NET.Test.Sdk

group FsCheck3
source https://api.nuget.org/v3/index.json
Expand Down
Loading
Loading