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
33 changes: 31 additions & 2 deletions Expecto.Tests/Main.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,42 @@ module Main

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

let serviceName = "Expecto.Tests"

let logger = Log.create serviceName


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


let logger = Log.create "Expecto.Tests"

[<EntryPoint>]
let main args =
let activitySource = new ActivitySource(serviceName)
use traceProvider =
Sdk
.CreateTracerProviderBuilder()
.AddSource(serviceName)
.SetResourceBuilder(resourceBuilder )
.AddOtlpExporter()
.Build()
let tracer = traceProvider.GetTracer(serviceName)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is adding the OpenTelemetry Exporter to the test project, mostly for demo purposes. Could create a separate project if desired.

// use span = tracer.StartActiveSpan("Expecto.main")
use span = tracer.StartRootSpan("Expecto.main")
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"; CLIArguments.ActivitySource activitySource] args test
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here a consumer would pass in the ActivitySource. Otherwise they'd have to add the dedicated Expecto one to the .AddSource call on line 29.




3 changes: 2 additions & 1 deletion Expecto.Tests/paket.references
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
FsCheck
FsCheck
OpenTelemetry.Exporter.OpenTelemetryProtocol
117 changes: 110 additions & 7 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 All @@ -9,6 +10,20 @@ open Expecto.Logging.Message
open Helpers
open Mono.Cecil

//! The other option is to use a dedicated activity source for Expecto instead of adding it to the config
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One option is to have Expecto have a dedicate ActivitySource, the other is to add it the the Expecto config and allow a user to pass it along.


// module ActivitySource =

// let [<Literal>] serviceName = "Expecto" // Should be public so consumers have a strong name when adding Sources
// let private version = lazy (
// let assembly = typeof<FlatTest>.Assembly
// let version = assembly.GetName().Version
// version.ToString()
// )

// let internal activitySource = lazy new ActivitySource(serviceName, version.Value)


// TODO: make internal?
module Impl =

Expand Down Expand Up @@ -520,6 +535,11 @@ module Impl =
colour: ColourLevel
/// Split test names by `.` or `/`
joinWith: JoinWith
// One option is to allow the consumer to provide an activity source
// only problem is the only way to update the config is by using the CLIArguments currently
// we would have to add a new CLIArgument but that doesn't really work as it's not a reallyCLI option
// or have another way of updating the config after it's been created
activitySource : ActivitySource option
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned, we can have an activity source in the config but there's not really a function that lets people update the config.

It might be more tricky to do this option if people are using the dotnet test integration so maybe a dedicated source is appropriate.

}
static member defaultConfig =
{ runInParallel = true
Expand All @@ -546,6 +566,7 @@ module Impl =
noSpinner = false
colour = Colour8
joinWith = JoinWith.Dot
activitySource = None
}

member x.appendSummaryHandler handleSummary =
Expand All @@ -559,8 +580,63 @@ module Impl =
}
}

let inline internal setStatus (status : ActivityStatusCode) (span : Activity) =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of helper functions. Don't have to live here, just here for the moment.

if isNull span |> not then
span.SetStatus(status) |> ignore

let inline internal setExn (e : exn) (span : Activity) =
if isNull 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 internal setExnMarkFailed (e : exn) (span : Activity) =
if isNull span |> not then
setExn e span
span |> setStatus ActivityStatusCode.Error

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

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

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

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

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

let execTestAsync (ct:CancellationToken) config (test:FlatTest) : Async<TestSummary> =
async {
let span =
config.activitySource
|> createActivity (config.joinWith.format test.name)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the starting point for creating an span. Later we add additional information to it based on whether we pass/fail/ignore.

span |> setSourceLocation (config.locate test.test)

use span = start span
let w = Stopwatch.StartNew()
try
match test.shouldSkipEvaluation with
Expand Down Expand Up @@ -593,32 +669,59 @@ module Impl =
)
do! test fsConfig
w.Stop()
return TestSummary.single Passed (float w.ElapsedMilliseconds)
stop span
let result = Passed
addOutcome result span
setStatus ActivityStatusCode.Ok span
return TestSummary.single result (float w.ElapsedMilliseconds)
with
| :? AssertException as e ->
w.Stop()
stop span
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")
return TestSummary.single (Failed msg) (float w.ElapsedMilliseconds)
let result = Failed msg
addOutcome result span
setExnMarkFailed e span
return TestSummary.single result (float w.ElapsedMilliseconds)
| :? FailedException as e ->
w.Stop()
return TestSummary.single (Failed ("\n"+e.Message)) (float w.ElapsedMilliseconds)
stop span
let result = Failed ("\n"+e.Message)
addOutcome result span
setExnMarkFailed e span
return TestSummary.single result (float w.ElapsedMilliseconds)
| :? IgnoreException as e ->
w.Stop()
return TestSummary.single (Ignored e.Message) (float w.ElapsedMilliseconds)
stop span
let result = Ignored e.Message
addOutcome result span
setExn e span
return TestSummary.single result (float w.ElapsedMilliseconds)
| :? AggregateException as e when e.InnerExceptions.Count = 1 ->
w.Stop()
stop span
if e.InnerException :? IgnoreException then
return TestSummary.single (Ignored e.InnerException.Message) (float w.ElapsedMilliseconds)
let result = Ignored e.InnerException.Message
addOutcome result span
setExn e span
return TestSummary.single result (float w.ElapsedMilliseconds)
else
return TestSummary.single (Error e.InnerException) (float w.ElapsedMilliseconds)
let result = Error e.InnerException
addOutcome result span
setExnMarkFailed e span
return TestSummary.single result (float w.ElapsedMilliseconds)
| e ->
w.Stop()
return TestSummary.single (Error e) (float w.ElapsedMilliseconds)
stop span
let result = Error e
addOutcome result span
setExnMarkFailed e span
return TestSummary.single result (float w.ElapsedMilliseconds)
}

let private numberOfWorkers limit config =
Expand Down
4 changes: 4 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 @@ -444,6 +445,8 @@ module Tests =
| Append_Summary_Handler of SummaryHandler
/// Specify test names join character.
| JoinWith of split: string
// TODO This isn't really a CLIArgument but just to show a way of updating the config
| ActivitySource of ActivitySource

let options = [
"--sequenced", "Don't run the tests in parallel.", Args.none Sequenced
Expand Down Expand Up @@ -531,6 +534,7 @@ module Tests =
| Printer p -> fun o -> { o with printer = p }
| Verbosity l -> fun o -> { o with verbosity = l }
| Append_Summary_Handler (SummaryHandler h) -> fun o -> o.appendSummaryHandler h
| ActivitySource s -> fun o -> { o with activitySource = Option.ofObj s }

[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module ExpectoConfig =
Expand Down
1 change: 1 addition & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ nuget Hopac ~> 0.4
nuget DiffPlex ~> 1.5
nuget Mono.Cecil ~> 0.11
nuget BenchmarkDotNet ~> 0.13.5
nuget OpenTelemetry.Exporter.OpenTelemetryProtocol

group FsCheck3
source https://api.nuget.org/v3/index.json
Expand Down
83 changes: 68 additions & 15 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ NUGET
FSharp.Core (>= 4.2.3)
FSharp.Core (7.0.200)
Gee.External.Capstone (2.3)
Google.Protobuf (3.26.1)
System.Memory (>= 4.5.3) - restriction: || (&& (== net6.0) (>= net45)) (&& (== net6.0) (< net5.0)) (&& (== net6.0) (< netstandard2.0)) (== netstandard2.1)
System.Runtime.CompilerServices.Unsafe (>= 4.5.2) - restriction: || (&& (== net6.0) (< net5.0)) (== netstandard2.1)
Grpc.Core.Api (2.62)
Grpc.Net.Client (2.62)
Grpc.Net.Common (>= 2.62)
Microsoft.Extensions.Logging.Abstractions (>= 6.0)
System.Diagnostics.DiagnosticSource (>= 6.0.1) - restriction: || (&& (== net6.0) (>= net462)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.1)
Grpc.Net.Common (2.62)
Grpc.Core.Api (>= 2.62)
Hopac (0.5.1)
FSharp.Core (>= 4.5.2)
Iced (1.18)
Expand All @@ -50,22 +60,51 @@ NUGET
Microsoft.Diagnostics.Tracing.TraceEvent (3.0.8)
System.Runtime.CompilerServices.Unsafe (>= 5.0)
Microsoft.DotNet.PlatformAbstractions (3.1.6)
Microsoft.Extensions.DependencyInjection (7.0)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0)
Microsoft.Extensions.DependencyInjection.Abstractions (7.0)
Microsoft.Extensions.Logging (7.0)
Microsoft.Extensions.DependencyInjection (>= 7.0)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0)
Microsoft.Extensions.Logging.Abstractions (>= 7.0)
Microsoft.Extensions.Options (>= 7.0)
System.Diagnostics.DiagnosticSource (>= 7.0) - restriction: || (&& (== net6.0) (>= net462)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.1)
Microsoft.Extensions.Logging.Abstractions (7.0)
Microsoft.Extensions.Configuration (8.0)
Microsoft.Extensions.Configuration.Abstractions (>= 8.0)
Microsoft.Extensions.Primitives (>= 8.0)
Microsoft.Extensions.Configuration.Abstractions (8.0)
Microsoft.Extensions.Primitives (>= 8.0)
Microsoft.Extensions.Configuration.Binder (8.0.1)
Microsoft.Extensions.Configuration.Abstractions (>= 8.0)
Microsoft.Extensions.DependencyInjection (8.0)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0)
Microsoft.Extensions.DependencyInjection.Abstractions (8.0.1)
Microsoft.Extensions.Diagnostics.Abstractions (8.0)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0)
Microsoft.Extensions.Options (>= 8.0)
System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= net462)) (== netstandard2.1)
System.Diagnostics.DiagnosticSource (>= 8.0)
System.Memory (>= 4.5.5) - restriction: || (&& (== net6.0) (>= net462)) (== netstandard2.1)
Microsoft.Extensions.Options (7.0.1)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0)
Microsoft.Extensions.Primitives (>= 7.0)
Microsoft.Extensions.Primitives (7.0)
Microsoft.Extensions.Logging (8.0)
Microsoft.Extensions.DependencyInjection (>= 8.0)
Microsoft.Extensions.Logging.Abstractions (>= 8.0)
Microsoft.Extensions.Options (>= 8.0)
System.Diagnostics.DiagnosticSource (>= 8.0) - restriction: || (&& (== net6.0) (>= net462)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.1)
Microsoft.Extensions.Logging.Abstractions (8.0.1)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.1)
System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= net462)) (== netstandard2.1)
System.Memory (>= 4.5.5) - restriction: || (&& (== net6.0) (>= net462)) (== netstandard2.1)
Microsoft.Extensions.Logging.Configuration (8.0)
Microsoft.Extensions.Configuration (>= 8.0)
Microsoft.Extensions.Configuration.Abstractions (>= 8.0)
Microsoft.Extensions.Configuration.Binder (>= 8.0)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0)
Microsoft.Extensions.Logging (>= 8.0)
Microsoft.Extensions.Logging.Abstractions (>= 8.0)
Microsoft.Extensions.Options (>= 8.0)
Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0)
Microsoft.Extensions.Options (8.0.2)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0)
Microsoft.Extensions.Primitives (>= 8.0)
System.ComponentModel.Annotations (>= 5.0) - restriction: || (&& (== net6.0) (< netstandard2.1)) (== netstandard2.1)
Microsoft.Extensions.Options.ConfigurationExtensions (8.0)
Microsoft.Extensions.Configuration.Abstractions (>= 8.0)
Microsoft.Extensions.Configuration.Binder (>= 8.0)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0)
Microsoft.Extensions.Options (>= 8.0)
Microsoft.Extensions.Primitives (>= 8.0)
Microsoft.Extensions.Primitives (8.0)
System.Memory (>= 4.5.5) - restriction: || (&& (== net6.0) (>= net462)) (== netstandard2.1)
System.Runtime.CompilerServices.Unsafe (>= 6.0)
Microsoft.NETCore.Platforms (3.1) - restriction: || (== net6.0) (&& (== netstandard2.1) (>= netcoreapp2.0)) (&& (== netstandard2.1) (>= netcoreapp3.1))
Expand All @@ -75,14 +114,28 @@ NUGET
System.Security.AccessControl (>= 5.0)
System.Security.Principal.Windows (>= 5.0)
Mono.Cecil (0.11.4)
OpenTelemetry (1.8)
Microsoft.Extensions.Diagnostics.Abstractions (>= 8.0)
Microsoft.Extensions.Logging.Configuration (>= 8.0)
OpenTelemetry.Api.ProviderBuilderExtensions (>= 1.8)
OpenTelemetry.Api (1.8)
System.Diagnostics.DiagnosticSource (>= 8.0)
OpenTelemetry.Api.ProviderBuilderExtensions (1.8)
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0)
OpenTelemetry.Api (>= 1.8)
OpenTelemetry.Exporter.OpenTelemetryProtocol (1.8)
Google.Protobuf (>= 3.22.5 < 4.0)
Grpc.Net.Client (>= 2.52 < 3.0)
OpenTelemetry (>= 1.8)
Perfolizer (0.2.1)
System.Memory (>= 4.5.3)
System.Buffers (4.5.1) - restriction: == netstandard2.1
System.CodeDom (7.0)
System.Collections.Immutable (7.0)
System.Memory (>= 4.5.5) - restriction: || (&& (== net6.0) (>= net462)) (== netstandard2.1)
System.Runtime.CompilerServices.Unsafe (>= 6.0)
System.Diagnostics.DiagnosticSource (7.0.2) - restriction: || (&& (== net6.0) (>= net462)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.1)
System.ComponentModel.Annotations (5.0) - restriction: || (&& (== net6.0) (< netstandard2.1)) (== netstandard2.1)
System.Diagnostics.DiagnosticSource (8.0)
System.Memory (>= 4.5.5) - restriction: || (&& (== net6.0) (>= net462)) (== netstandard2.1)
System.Runtime.CompilerServices.Unsafe (>= 6.0)
System.Management (7.0)
Expand Down
Loading