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

Implement injection for events (fixes #56) #67

Merged
merged 5 commits into from
Jan 26, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFrameworks>net6.0;net452</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="Logger.fs" />
<Compile Include="NunitWiring.fs" />
<Compile Include="StockItem.fs" />
<EmbeddedResource Include="Stock.feature" />
Expand Down
22 changes: 22 additions & 0 deletions Examples/ByFeature/FunctionalInjection/Logger.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Logger

open TickSpec
open System

type LogMessages = string list

type LoggerContext = { Messages: LogMessages }

let [<BeforeScenario>] setup () =
{ Messages = "Before scenario" |> List.singleton }

let [<BeforeStep>] beforeStep (previousMessages: LoggerContext) =
{ Messages = "Before step" :: previousMessages.Messages }

let [<AfterStep>] afterStep (previousMessages: LoggerContext) =
{ Messages = "After step" :: previousMessages.Messages }

let [<AfterScenario>] afterScenario (previousMessages: LoggerContext) =
"After scenario" :: previousMessages.Messages
|> Seq.rev
|> Seq.iter Console.WriteLine
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#### 2.0.3 (To be released)
* [Feature] Makes `ScenarioInformation` available to step implementations through arguments resolution (issue #49)
* [Fix] Performance improvements when the used assembly contains many methods (issue #45)
* [Feature] Add support for functional injection in events (issue #56)
* [Fix] Keep empty lines in doc strings (issue #60)
* [Fix] Allow multiple step types on a single method (issue #55)
* [Fix] Unit test serialization to allow running from IDE easily
Expand Down
64 changes: 42 additions & 22 deletions TickSpec/ScenarioGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,38 @@ let emitArgument
else
emitValue gen providerField parsers paramType arg

/// Emit arguments through injection
let emitInjectionArguments
(gen:ILGenerator)
(providerField:FieldBuilder)
(parameters: ParameterInfo array) =
parameters
|> Array.iter (fun p -> emitInstance gen providerField p.ParameterType)

let storeMethodResultInProvider
(gen:ILGenerator)
(providerField:FieldBuilder)
(mi:MethodInfo) =
if mi.ReturnType <> typeof<System.Void> then
gen.Emit(OpCodes.Box,mi.ReturnType)
let local0 = gen.DeclareLocal(typeof<Object>).LocalIndex
gen.Emit(OpCodes.Stloc, local0)

if FSharpType.IsTuple mi.ReturnType then
let types = FSharpType.GetTupleElements mi.ReturnType
for i = 0 to (types.Length - 1) do
let t = types.[i]
let local1 = gen.DeclareLocal(typeof<Object>).LocalIndex

gen.Emit(OpCodes.Ldloc, local0)
gen.Emit(OpCodes.Ldc_I4, i)
gen.EmitCall(OpCodes.Call, typeof<Microsoft.FSharp.Reflection.FSharpValue>.GetMethod("GetTupleField"), null)
gen.Emit(OpCodes.Stloc, local1)

emitRegisterInstanceCall gen t local1 providerField
else
emitRegisterInstanceCall gen (mi.ReturnType) local0 providerField

/// Defines step method
let defineStepMethod
doc
Expand Down Expand Up @@ -442,35 +474,18 @@ let defineStepMethod
let bulletsCount = line.Bullets |> Option.count
let docCount = line.Doc |> Option.count
args.Length + tableCount + bulletsCount + docCount
Array.sub ps a (ps.Length - a)
|> Array.iter (fun (p:ParameterInfo) ->
emitInstance gen providerField p.ParameterType)

ps
|> Array.skip a
|> emitInjectionArguments gen providerField

// Emit method invoke
if mi.IsStatic then
gen.EmitCall(OpCodes.Call, mi, null)
else
gen.Emit(OpCodes.Callvirt, mi)

if mi.ReturnType <> typeof<System.Void> then
gen.Emit(OpCodes.Box,mi.ReturnType)
let local0 = gen.DeclareLocal(typeof<Object>).LocalIndex
gen.Emit(OpCodes.Stloc, local0)

if FSharpType.IsTuple mi.ReturnType then
let types = FSharpType.GetTupleElements mi.ReturnType
for i = 0 to (types.Length - 1) do
let t = types.[i]
let local1 = gen.DeclareLocal(typeof<Object>).LocalIndex

gen.Emit(OpCodes.Ldloc, local0)
gen.Emit(OpCodes.Ldc_I4, i)
gen.EmitCall(OpCodes.Call, typeof<Microsoft.FSharp.Reflection.FSharpValue>.GetMethod("GetTupleField"), null)
gen.Emit(OpCodes.Stloc, local1)

emitRegisterInstanceCall gen t local1 providerField
else
emitRegisterInstanceCall gen (mi.ReturnType) local0 providerField
storeMethodResultInProvider gen providerField mi

// Emit return
gen.Emit(OpCodes.Ret)
Expand All @@ -495,11 +510,16 @@ let defineRunMethod
// Emit event methods
let emitEvents =
Seq.iter (fun (mi:MethodInfo) ->
mi.GetParameters()
|> emitInjectionArguments gen providerField

if mi.IsStatic then
gen.EmitCall(OpCodes.Call, mi, null)
else
emitInstance gen providerField mi.DeclaringType
gen.EmitCall(OpCodes.Callvirt, mi, null)

storeMethodResultInProvider gen providerField mi
)

beforeScenarioEvents |> emitEvents
Expand Down
23 changes: 12 additions & 11 deletions TickSpec/ScenarioRun.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@ let getInstance (provider:IInstanceProvider) (m:MethodInfo) =
else provider.GetService m.DeclaringType

/// Invokes specified method with specified parameters
let invoke (provider:IInstanceProvider) (m:MethodInfo) ps =
let invoke (provider:IInstanceProvider) (m: MethodInfo) (ps: obj array) =
let args = [|
yield! ps

for p in m.GetParameters() |> Seq.skip ps.Length do
yield provider.GetService p.ParameterType
|]

let instance = getInstance provider m
let ret = m.Invoke(instance,ps)
let ret = m.Invoke(instance, args)
if m.ReturnType <> typeof<System.Void> then
if FSharpType.IsTuple m.ReturnType then
let types = FSharpType.GetTupleElements m.ReturnType
Expand Down Expand Up @@ -148,15 +155,9 @@ let invokeStep
else failwithf "Expected a Table or array argument at position %d" args.Length
| None,None,Some doc -> [|box doc|]
| _,_,_ -> [||]
let args =
let stArgs = Array.append args tail
let injectionArgs =
let pars = meth.GetParameters()
let a = stArgs.Length
Array.sub pars a (pars.Length - a)
|> Array.map (fun (p:ParameterInfo) -> provider.GetService(p.ParameterType))
Array.append stArgs injectionArgs
invoke provider meth args

Array.append args tail
|> invoke provider meth

/// Generate scenario execution function
let generate events parsers (scenario: ScenarioMetadata, lines) (serviceProviderFactory: Ref<unit -> IInstanceProvider>) =
Expand Down
Loading