e6e2ca23bf
* add Mermaid formatted sequence diagram * add draft engine interfaces and sequence diagram * A post-create script that gets executed when the container first launches * Example of using Aspect-oriented programming to start test engine * wired up engine lifecycle manager * reformat to C# bracket style * add additional lifecycle hooks for more granualarity * Create StateMachineExample.cs * WIP state machine * Lifecycle diagram * API, example test, state diagram. * close to done on example code diagram and doc * More examples and text updates * Update engine_lifecycle_spike.md * minor refactoring * minor refactorings * minor refactorings * minor refactorings, add awaits * update markdown doc and minor refactoring to example code * remove unused code * update doc * Make sure code compiles. Minor updates to wording * refactoring and aligning doc to code * delete duplicate code in /sequence_diagram.md * delete unused code * update casing to match convention * comment strings added to design class * remove unused code, rename main * move Attributes folder to designs Co-authored-by: Uffaz <uffaz@Uffazs-MacBook-Pro.local> Co-authored-by: Uffaz Nathaniel <ThePedestrian@users.noreply.github.com> |
||
---|---|---|
.. | ||
Attributes | ||
BenchPress.cs | ||
IAllocateResourceGroup.cs | ||
ICreateResource.cs | ||
ILifecycleManager.cs | ||
IValidateResource.cs | ||
README.md |
README.md
Benchpress Testing Framework Design
Overview
The core Benchpress engine proposes an Inversion of Control (IoC) design paradigm. In this design, we transfer the control of objects or portions of a program to a container or framework for orchestrating and execution.
Few advantages of IoC design is that it enables test writers to use any language-specific framework of their choice. For example, in C# NUnit, XUnit, and MSTest are some of the most popular choices.
The primary goal of the Benchpress testing framework is to start and stop the Core testing Engine in a thread-safe manner, while also supporting injecting optional runtime configuration into the engine.
Some examples of runtime configuration that the framework may provide to the engine include:
- Number of automatic restarts due to exception conditions the engine should undergo before reporting a fatal error to the framework.
- Number of automatic retries should the engine make when interacting over the network before reporting a fatal error to the framework.
- How long of a time period should the engine wait during network interactions before entering a timeout condition and reporting a fatal error to the framework.
Our API allows test authors to include declarative decorators/annotations/attributes on their test methods and/or test classes that will intercept the flow-of-control at runtime to execute the various pre/post lifecycle management steps - including ensuring an engine process instance is available - allowing for massively concurrent test execution.
State Diagram
Internally the testing framework has a state machine that allows it to track and execute events and test methods.
flowchart TD
PreInitialization --> Initialization
Initialization --> EngineStarting
EngineStarting -->|Success| EngineStartSuccess
EngineStarting -->|Failure| EngineStartFailure
EngineStartSuccess --> TestExecute
TestExecute -->|1...n| TestExecute
EngineStartFailure --> ShutDown
TestExecute --> ShutDown
ShutDown --> EngineShutdownSuccess
EngineShutdownSuccess --> Done
Public API
The public API only needs to be called in frameworks/languages where IoC is not possible, e.g., Powershell/Pester. In such cases, the expectation is that users will call Start()
to configure the testing enviornment and on test teardown call Stop()
.
Init()
- Init singleton instancePreEngineStart
- Optional ConfigurationStartEngine()
- Start the engineStopEngine()
- Stop the engine
Example Test
Method-level
class Test1
{
// Annotate at method level
[BenchpressTest]
public void TestRG()
{
var result = Benchpress.DoesResourceGroupExist("my-rg");
Debug.Assert(result, true);
}
// Annotate at method level
[BenchpressTest]
public void Test_ResourceGroupExists()
{
var result = Benchpress.DoesResourceGroupExist("my-rg");
Debug.Assert(result, true);
}
}
Class-level
// Annotate at class level
[BenchpressTest]
class Test2
{
public void TestRG()
{
var result = Benchpress.DoesResourceGroupExist("my-rg");
Debug.Assert(result, true);
}
public void Test_ResourceGroupExists()
{
var result = Benchpress.DoesResourceGroupExist("my-rg");
Debug.Assert(result, true);
}
}
Subscribe to life-cycle events
// Listen to life-cycle events
[BenchpressTest]
class Test3
{
[Initization]
public void OnInitization() { /* ... */ }
[EngineStartSuccess]
public void OnEngineStartSuccess() { /* ... */ }
[EngineStartFailure]
public void OnEngineStartFailure() { /* ... */ }
[TestExecute]
public void OnTestExecute(MethodInfo method) { /* ... */ }
[Shutdown]
public void OnShutdown() { /* ... */ }
[Done]
public void OnDone() { /* ... */ }
[Fact]
public void TestResourceGroupExists()
{
var result = Benchpress.DoesStorageAccountExists("mystorage");
Debug.Assert(result == true, "Test failed");
}
[Fact]
public void TestStorageAccountPolicy()
{
var result = Benchpress.DoesStorageAccountExists("mystorage--222");
Debug.Assert(result == true, "Test failed");
}
}
Lifecycle Event Descriptions
Init() // Called once at the very start of test execution before the engine is started. This method can be used to perform or pre-execution steps.
PreEngineStart(RetryCount = 3, HttpTimeout = 60000, KeepAlive = true, ...more configuration) – Called once to start the engine if not already started
StartEngine() // Performs resource locking and starts the engine
OnEngineStartSuccess() // Called once if the engine is started successfully
OnEngineStartFailure() // Called once the engine is started successfully.
PreTestExecute(method) // Called once before each test executes.
OnTestExecute(method) // Called once after the test executes.
PreTestExecute(method) // Called once before each test executes.
StopEngine() // Performs resource locking and stops the engine
TearDown() // Called once after the engine is stopped and before Done() is called
Done() // Called at the end once of framework
Proposed Sequence Diagram
sequenceDiagram
Runtime/OS->>+Engine: main([...args])
Engine->Engine: Init
Engine->Engine: If RestartCount>2 then Destroy
Engine->Engine: Setup
par Engine Ready State. ASync Comms allowed
Runtime/OS->>+Engine: Shutdown Signal
Engine->Engine: Destroy
Engine-->>-Runtime/OS: $EXIT_CODE
and
Engine->Engine: Exceptional Condition
Engine->Engine: Teardown
and
Framework->>+Engine: Create Resource Group given $AZURE_SUBSCRIPTION_ID
Engine->>+Azure: Create Resource Group
Azure-->>-Engine:
Engine->>-Framework: Resource Group Creation Status
and
Framework->>+Engine: Create Resource(s) given $RESOURCE_GROUP_NAME
Engine->>+Azure: Create Resource(s)
Azure-->>-Engine:
Engine-->>-Framework:
and
Framework->>+Engine: Validate Resource(s) given $VALID_STAE
Engine->>+Azure: Get Resource(s) State(s)
Azure-->>-Engine:
Engine-->>-Framework:
end
Engine->Engine: Teardown
Engine->Engine: If AutoRestart==True then Setup
Engine->Engine: Destroy
Engine-->>-Runtime/OS: $EXIT_CODE