Skip to content

Commit

Permalink
Add support for timers and grouping requests
Browse files Browse the repository at this point in the history
  • Loading branch information
rabelenda committed May 3, 2024
1 parent cf18b48 commit edec0aa
Show file tree
Hide file tree
Showing 24 changed files with 635 additions and 4 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ dotnet_diagnostic.IDE0058.severity = none
dotnet_diagnostic.IDE0063.severity = none
dotnet_diagnostic.IDE0065.severity = none
dotnet_diagnostic.IDE0090.severity = none
dotnet_diagnostic.IDE0270.severity = none
dotnet_diagnostic.JSON002.severity = none
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Abstracta.JmeterDsl.Core.Controllers
{
using static JmeterDsl;

public class DslSimpleControllerTest
{
[Test]
public void ShouldApplyAssertionToSimpleControllerScopedElements()
{
var body = "FAIL";
var stats = TestPlan(
ThreadGroup(1, 1,
SimpleController(
ResponseAssertion()
.ContainsSubstrings("OK"),
DummySampler(body),
DummySampler(body)
),
DummySampler(body)
)
).Run();
Assert.That(stats.Overall.ErrorsCount, Is.EqualTo(2));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Abstracta.JmeterDsl.Core.Controllers
{
using static JmeterDsl;

public class DslTransactionControllerTest
{
[Test]
public void ShouldIncludeTransactionSampleInResultsWhenTestPlanWithTransaction()
{
TestPlanStats stats = TestPlan(
ThreadGroup(1, 1,
Transaction("My Transaction",
DummySampler("ok")
)
)
).Run();
Assert.That(stats.Overall.SamplesCount, Is.EqualTo(2));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;

namespace Abstracta.JmeterDsl.Core.Samplers
{
using static JmeterDsl;

public class DslFlowControlActionTest
{
[Test]
public void ShouldLastAtLeastConfiguredTimeWhenUsingConstantTimer()
{
var timerDuration = TimeSpan.FromSeconds(5);
var stats = TestPlan(
ThreadGroup(1, 1,
ThreadPause(timerDuration),
DummySampler("OK")
)
).Run();
Assert.That(stats.Duration, Is.GreaterThan(timerDuration));
}
}
}
21 changes: 21 additions & 0 deletions Abstracta.JmeterDsl.Tests/Core/Timers/DslConstantTimerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Abstracta.JmeterDsl.Core.Timers
{
using System;
using static JmeterDsl;

public class DslConstantTimerTest
{
[Test]
public void ShouldLastAtLeastConfiguredTimeWhenUsingConstantTimer()
{
var timerDuration = TimeSpan.FromSeconds(5);
var stats = TestPlan(
ThreadGroup(1, 1,
ConstantTimer(timerDuration),
DummySampler("OK")
)
).Run();
Assert.That(stats.Duration, Is.GreaterThan(timerDuration));
}
}
}
21 changes: 21 additions & 0 deletions Abstracta.JmeterDsl.Tests/Core/Timers/DslUniformRandomTimerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Abstracta.JmeterDsl.Core.Timers
{
using System;
using static JmeterDsl;

public class DslUniformRandomTimerTest
{
[Test]
public void ShouldLastAtLeastMinimumTimeWhenUsingRandomUniformTimer()
{
var minimum = TimeSpan.FromSeconds(5);
var stats = TestPlan(
ThreadGroup(1, 1,
UniformRandomTimer(minimum, TimeSpan.FromSeconds(7)),
DummySampler("OK")
)
).Run();
Assert.That(stats.Duration, Is.GreaterThan(minimum));
}
}
}
12 changes: 9 additions & 3 deletions Abstracta.JmeterDsl/Core/Bridge/BridgedObjectConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,15 @@ public void WriteYaml(IEmitter emitter, object value, Type type)
emitter.Emit(new MappingEnd());
}

private string BuildTagName(Type valueType) =>
"!" + (IsCoreElement(valueType) ? BuildSimpleTagName(valueType)
: BuildCompleteTagName(valueType));
private string BuildTagName(Type valueType)
{
var ret = valueType.GetCustomAttribute<YamlTypeAttribute>()?.TagName;
if (ret == null)
{
ret = IsCoreElement(valueType) ? BuildSimpleTagName(valueType) : BuildCompleteTagName(valueType);
}
return "!" + ret;
}

private bool IsCoreElement(Type valueType)
{
Expand Down
10 changes: 10 additions & 0 deletions Abstracta.JmeterDsl/Core/Bridge/YamlTypeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Abstracta.JmeterDsl.Core.Bridge
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class YamlTypeAttribute : Attribute
{
public string TagName { get; set; }
}
}
21 changes: 21 additions & 0 deletions Abstracta.JmeterDsl/Core/Controllers/DslSimpleController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Abstracta.JmeterDsl.Core.ThreadGroups;

namespace Abstracta.JmeterDsl.Core.Controllers
{
/// <summary>
/// Builds a Simple Controller that allows defining new JMeter scope for other elements to apply.
/// <br/>
/// This is handy for example to apply timers, configs, listeners, assertions, pre- and
/// post-processors to only some samplers in the test plan.
/// <br/>
/// It has a similar functionality as the transaction controller, but it doesn't add any additional
/// sample results (statistics) to the test plan.
/// </summary>
public class DslSimpleController : BaseController<DslSimpleController>
{
public DslSimpleController(string name, IThreadGroupChild[] children)
: base(name, children)
{
}
}
}
71 changes: 71 additions & 0 deletions Abstracta.JmeterDsl/Core/Controllers/DslTransactionController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Abstracta.JmeterDsl.Core.Bridge;
using Abstracta.JmeterDsl.Core.ThreadGroups;

namespace Abstracta.JmeterDsl.Core.Controllers
{
/// <summary>
/// Allows specifying JMeter transaction controllers which group different samples associated to same
/// transaction.
/// <br/>
/// This is usually used when grouping different steps of a flow, for example group requests of login
/// flow, adding item to cart, purchase, etc. It provides aggregate metrics of all it's samples.
/// </summary>
[YamlType(TagName = "transaction")]
public class DslTransactionController : BaseController<DslTransactionController>
{
private bool _includeTimersAndProcessorsTime;
private bool _generateParentSample;

public DslTransactionController(string name, IThreadGroupChild[] children)
: base(name, children)
{
}

/// <summary>
/// Specifies to include time spent in timers and pre- and post-processors in sample results.
/// </summary>
/// <returns>the controller for further configuration or usage.</returns>
public DslTransactionController IncludeTimersAndProcessorsTime() =>
IncludeTimersAndProcessorsTime(true);

/// <summary>
/// Same as <see cref="IncludeTimersAndProcessorsTime()"/> but allowing to enable or disable it.
/// <br/>
/// This is helpful when the resolution is taken at runtime.
/// </summary>
/// <param name="enable">specifies to enable or disable the setting. By default, it is set to false.</param>
/// <returns>the controller for further configuration or usage.</returns>
/// <seealso cref="IncludeTimersAndProcessorsTime()"/>
public DslTransactionController IncludeTimersAndProcessorsTime(bool enable)
{
_includeTimersAndProcessorsTime = enable;
return this;
}

/// <summary>
/// Specifies to create a sample result as parent of children samplers.
/// <br/>
/// It is useful in some scenarios to get transaction sample results as parent of children samplers
/// to focus mainly in transactions and not be concerned about each particular request. Enabling
/// parent sampler helps in this regard, only reporting the transactions in summary reports, and
/// not the transaction children results.
/// </summary>
/// <returns>the controller for further configuration or usage.</returns>
public DslTransactionController GenerateParentSample() =>
GenerateParentSample(true);

/// <summary>
/// Same as <see cref="GenerateParentSample()"/> but allowing to enable or disable it.
/// <br/>
/// This is helpful when the resolution is taken at runtime.
/// </summary>
/// <param name="enable">specifies to enable or disable the setting. By default, it is set to false.</param>
/// <returns>the controller for further configuration or usage.</returns>
/// <seealso cref="GenerateParentSample()"/>
public DslTransactionController GenerateParentSample(bool enable)
{
_generateParentSample = enable;
return this;
}
}
}
22 changes: 22 additions & 0 deletions Abstracta.JmeterDsl/Core/Samplers/DslFlowControlAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Abstracta.JmeterDsl.Core.Bridge;

namespace Abstracta.JmeterDsl.Core.Samplers
{
/// <summary>
/// Uses JMeter Flow Control Action to allow taking different actions (stop, pause, interrupt).
/// </summary>
[YamlType(TagName = "threadPause")]
public class DslFlowControlAction : BaseSampler<DslFlowControlAction>
{
private readonly string _duration;

private DslFlowControlAction(string duration)
: base(null)
{
_duration = duration;
}

public static DslFlowControlAction PauseThread(string duration) =>
new DslFlowControlAction(duration);
}
}
15 changes: 15 additions & 0 deletions Abstracta.JmeterDsl/Core/Timers/BaseTimer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Abstracta.JmeterDsl.Core.TestElements;

namespace Abstracta.JmeterDsl.Core.Timers
{
/// <summary>
/// Contains common logic for all timers.
/// </summary>
public abstract class BaseTimer : BaseTestElement, IMultiLevelTestElement
{
public BaseTimer(string name)
: base(name)
{
}
}
}
24 changes: 24 additions & 0 deletions Abstracta.JmeterDsl/Core/Timers/DslConstantTimer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Abstracta.JmeterDsl.Core.Timers
{
/// <summary>
/// Allows using JMeter Constant Timers which pause the thread for a given period.
/// <br/>
/// The pause calculated by the timer will be applied after samplers pre-processors execution and
/// before actual sampling.
/// <br/>
/// Take into consideration that timers applies to all samplers in their scope: if added at test plan
/// level, it will apply to all samplers in test plan; if added at thread group level, it will apply
/// only to samples in such thread group; if added as child of a sampler, it will only apply to that
/// sampler.
/// </summary>
public class DslConstantTimer : BaseTimer
{
private readonly string _duration;

public DslConstantTimer(string duration)
: base(null)
{
_duration = duration;
}
}
}
14 changes: 14 additions & 0 deletions Abstracta.JmeterDsl/Core/Timers/DslSynchronizingTimer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Abstracta.JmeterDsl.Core.Timers
{
/// <summary>
/// Uses JMeter Synchronizing Timer to allow sending a batch of requests simultaneously to a system
/// under test.
/// </summary>
public class DslSynchronizingTimer : BaseTimer
{
public DslSynchronizingTimer()
: base(null)
{
}
}
}
29 changes: 29 additions & 0 deletions Abstracta.JmeterDsl/Core/Timers/DslUniformRandomTimer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;

namespace Abstracta.JmeterDsl.Core.Timers
{
/// <summary>
/// Allows specifying JMeter Uniform Random Timers which pause the thread with a random time with
/// uniform distribution.
/// <br/>
/// The pause calculated by the timer will be applied after samplers pre-processors execution and
/// before actual sampling.
/// <br/>
/// Take into consideration that timers applies to all samplers in their scope: if added at test plan
/// level, it will apply to all samplers in test plan; if added at thread group level, it will apply
/// only to samples in such thread group; if added as child of a sampler, it will only apply to that
/// sampler.
/// </summary>
public class DslUniformRandomTimer : BaseTimer
{
private TimeSpan _minimum;
private TimeSpan _maximum;

public DslUniformRandomTimer(TimeSpan minimum, TimeSpan maximum)
: base(null)
{
_minimum = minimum;
_maximum = maximum;
}
}
}
Loading

0 comments on commit edec0aa

Please sign in to comment.