Skip to content

Commit c47d7e7

Browse files
mfahadahmedMichael Ng
authored andcommitted
feat(eventProcessor): Adds EventProcessor and BatchEventProcessor (#186)
1 parent ae9c8dd commit c47d7e7

File tree

14 files changed

+858
-12
lines changed

14 files changed

+858
-12
lines changed

OptimizelySDK.Net35/OptimizelySDK.Net35.csproj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@
7979
<Compile Include="..\OptimizelySDK\Entity\Event.cs">
8080
<Link>Entity\Event.cs</Link>
8181
</Compile>
82-
<Compile Include="..\OptimizelySDK\Entity\EventAttributes.cs">
83-
<Link>Entity\EventAttributes.cs</Link>
82+
<Compile Include="..\OptimizelySDK\Entity\EventTags.cs">
83+
<Link>Entity\EventTags.cs</Link>
8484
</Compile>
8585
<Compile Include="..\OptimizelySDK\Entity\Experiment.cs">
8686
<Link>Entity\Experiment.cs</Link>
@@ -266,6 +266,9 @@
266266
<Compile Include="..\OptimizelySDK\Event\UserEventFactory.cs">
267267
<Link>Event\UserEventFactory.cs</Link>
268268
</Compile>
269+
<Compile Include="..\OptimizelySDK\Event\EventProcessor.cs">
270+
<Link>Event\EventProcessor.cs</Link>
271+
</Compile>
269272
</ItemGroup>
270273
<ItemGroup>
271274
<None Include="..\OptimizelySDK\Utils\schema.json">

OptimizelySDK.Net40/OptimizelySDK.Net40.csproj

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@
8080
<Compile Include="..\OptimizelySDK\Entity\Event.cs">
8181
<Link>Entity\Event.cs</Link>
8282
</Compile>
83-
<Compile Include="..\OptimizelySDK\Entity\EventAttributes.cs">
84-
<Link>Entity\EventAttributes.cs</Link>
83+
<Compile Include="..\OptimizelySDK\Entity\EventTags.cs">
84+
<Link>Entity\EventTags.cs</Link>
8585
</Compile>
8686
<Compile Include="..\OptimizelySDK\Entity\Experiment.cs">
8787
<Link>Entity\Experiment.cs</Link>
@@ -276,6 +276,12 @@
276276
<Compile Include="..\OptimizelySDK\Event\UserEventFactory.cs">
277277
<Link>Event\UserEventFactory.cs</Link>
278278
</Compile>
279+
<Compile Include="..\OptimizelySDK\Event\BatchEventProcessor.cs">
280+
<Link>Event\BatchEventProcessor.cs</Link>
281+
</Compile>
282+
<Compile Include="..\OptimizelySDK\Event\EventProcessor.cs">
283+
<Link>Event\EventProcessor.cs</Link>
284+
</Compile>
279285
</ItemGroup>
280286
<ItemGroup>
281287
<None Include="..\OptimizelySDK\Utils\schema.json">

OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
1111
</PropertyGroup>
12+
<PropertyGroup>
13+
<DefineConstants>;$(DefineConstants);<NETSTANDARD>NetStandardIdentifier</NETSTANDARD>
14+
</DefineConstants>
15+
</PropertyGroup>
1216
<ItemGroup>
1317
<Compile Include="..\OptimizelySDK\AudienceConditions\AndCondition.cs" />
1418
<Compile Include="..\OptimizelySDK\AudienceConditions\AudienceIdCondition.cs" />
@@ -21,7 +25,7 @@
2125
<Compile Include="..\OptimizelySDK\Entity\Audience.cs" />
2226
<Compile Include="..\OptimizelySDK\Entity\Entity.cs" />
2327
<Compile Include="..\OptimizelySDK\Entity\Event.cs" />
24-
<Compile Include="..\OptimizelySDK\Entity\EventAttributes.cs" />
28+
<Compile Include="..\OptimizelySDK\Entity\EventTags.cs" />
2529
<Compile Include="..\OptimizelySDK\Entity\Experiment.cs" />
2630
<Compile Include="..\OptimizelySDK\Entity\FeatureDecision.cs" />
2731
<Compile Include="..\OptimizelySDK\Entity\ForcedVariation.cs" />
@@ -116,6 +120,9 @@
116120
<Compile Include="..\OptimizelySDK\Event\UserEventFactory.cs">
117121
<Link>UserEventFactory.cs</Link>
118122
</Compile>
123+
<Compile Include="..\OptimizelySDK\Event\EventProcessor.cs">
124+
<Link>EventProcessor.cs</Link>
125+
</Compile>
119126
</ItemGroup>
120127
<ItemGroup>
121128
<EmbeddedResource Include="..\OptimizelySDK\Utils\schema.json">

OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@
8585
<Compile Include="..\OptimizelySDK\Entity\Event.cs">
8686
<Link>Entity\Event.cs</Link>
8787
</Compile>
88-
<Compile Include="..\OptimizelySDK\Entity\EventAttributes.cs">
89-
<Link>Entity\EventAttributes.cs</Link>
88+
<Compile Include="..\OptimizelySDK\Entity\EventTags.cs">
89+
<Link>Entity\EventTags.cs</Link>
9090
</Compile>
9191
<Compile Include="..\OptimizelySDK\Entity\Experiment.cs">
9292
<Link>Entity\Experiment.cs</Link>
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
using Moq;
2+
using NUnit.Framework;
3+
using OptimizelySDK.Config;
4+
using OptimizelySDK.Entity;
5+
using OptimizelySDK.Event;
6+
using OptimizelySDK.Event.Dispatcher;
7+
using OptimizelySDK.Event.Entity;
8+
using OptimizelySDK.Logger;
9+
using OptimizelySDK.Notifications;
10+
using OptimizelySDK.Tests.NotificationTests;
11+
using System;
12+
using System.Collections.Concurrent;
13+
using System.Threading;
14+
15+
namespace OptimizelySDK.Tests.EventTests
16+
{
17+
[TestFixture]
18+
class BatchEventProcessorTest
19+
{
20+
private static string TestUserId = "testUserId";
21+
private const string EventName = "purchase";
22+
23+
public const int MAX_BATCH_SIZE = 10;
24+
public const int MAX_DURATION_MS = 1000;
25+
public const int TIMEOUT_INTERVAL_MS = 5000;
26+
27+
private ProjectConfig Config;
28+
private Mock<ILogger> LoggerMock;
29+
private BlockingCollection<object> eventQueue;
30+
private BatchEventProcessor EventProcessor;
31+
private Mock<IEventDispatcher> EventDispatcherMock;
32+
private TestEventDispatcher TestEventDispatcher;
33+
34+
[SetUp]
35+
public void Setup()
36+
{
37+
LoggerMock = new Mock<ILogger>();
38+
LoggerMock.Setup(l => l.Log(It.IsAny<LogLevel>(), It.IsAny<string>()));
39+
40+
Config = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, new ErrorHandler.NoOpErrorHandler());
41+
42+
eventQueue = new BlockingCollection<object>(100);
43+
EventDispatcherMock = new Mock<IEventDispatcher>();
44+
}
45+
46+
[TearDown]
47+
public void TearDown()
48+
{
49+
EventProcessor.Stop();
50+
}
51+
52+
[Test]
53+
public void TestDrainOnClose()
54+
{
55+
var eventDispatcher = new TestEventDispatcher();
56+
SetEventProcessor(eventDispatcher);
57+
58+
UserEvent userEvent = BuildConversionEvent(EventName);
59+
EventProcessor.Process(userEvent);
60+
eventDispatcher.ExpectConversion(EventName, TestUserId);
61+
62+
Thread.Sleep(1500);
63+
64+
Assert.True(eventDispatcher.CompareEvents());
65+
Assert.AreEqual(0, EventProcessor.EventQueue.Count);
66+
}
67+
68+
[Test]
69+
public void TestFlushOnMaxTimeout()
70+
{
71+
var countdownEvent = new CountdownEvent(1);
72+
var eventDispatcher = new TestEventDispatcher(countdownEvent);
73+
SetEventProcessor(eventDispatcher);
74+
75+
UserEvent userEvent = BuildConversionEvent(EventName);
76+
EventProcessor.Process(userEvent);
77+
eventDispatcher.ExpectConversion(EventName, TestUserId);
78+
79+
Thread.Sleep(1500);
80+
81+
Assert.True(eventDispatcher.CompareEvents());
82+
Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), "Exceeded timeout waiting for notification.");
83+
Assert.AreEqual(0, EventProcessor.EventQueue.Count);
84+
}
85+
86+
[Test]
87+
public void TestFlushMaxBatchSize()
88+
{
89+
var countdownEvent = new CountdownEvent(1);
90+
var eventDispatcher = new TestEventDispatcher(countdownEvent) { Logger = LoggerMock.Object };
91+
SetEventProcessor(eventDispatcher);
92+
93+
for (int i = 0; i < MAX_BATCH_SIZE; i++)
94+
{
95+
UserEvent userEvent = BuildConversionEvent(EventName);
96+
EventProcessor.Process(userEvent);
97+
eventDispatcher.ExpectConversion(EventName, TestUserId);
98+
}
99+
100+
Thread.Sleep(1000);
101+
102+
Assert.True(eventDispatcher.CompareEvents());
103+
countdownEvent.Wait();
104+
Assert.AreEqual(0, EventProcessor.EventQueue.Count);
105+
106+
LoggerMock.Verify(l => l.Log(LogLevel.ERROR, It.IsAny<string>()), Times.Never);
107+
}
108+
109+
[Test]
110+
public void TestFlush()
111+
{
112+
var countdownEvent = new CountdownEvent(2);
113+
var eventDispatcher = new TestEventDispatcher(countdownEvent);
114+
SetEventProcessor(eventDispatcher);
115+
116+
UserEvent userEvent = BuildConversionEvent(EventName);
117+
EventProcessor.Process(userEvent);
118+
EventProcessor.Flush();
119+
eventDispatcher.ExpectConversion(EventName, TestUserId);
120+
121+
EventProcessor.Process(userEvent);
122+
EventProcessor.Flush();
123+
eventDispatcher.ExpectConversion(EventName, TestUserId);
124+
125+
Thread.Sleep(1500);
126+
127+
Assert.True(eventDispatcher.CompareEvents());
128+
Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS / 2)), "Exceeded timeout waiting for notification.");
129+
Assert.AreEqual(0, EventProcessor.EventQueue.Count);
130+
}
131+
132+
[Test]
133+
public void TestFlushOnMismatchRevision()
134+
{
135+
var countdownEvent = new CountdownEvent(2);
136+
var eventDispatcher = new TestEventDispatcher(countdownEvent);
137+
SetEventProcessor(eventDispatcher);
138+
139+
Config.Revision = "1";
140+
Config.ProjectId = "X";
141+
var userEvent1 = BuildConversionEvent(EventName, Config);
142+
EventProcessor.Process(userEvent1);
143+
eventDispatcher.ExpectConversion(EventName, TestUserId);
144+
145+
Config.Revision = "2";
146+
Config.ProjectId = "X";
147+
var userEvent2 = BuildConversionEvent(EventName, Config);
148+
EventProcessor.Process(userEvent2);
149+
eventDispatcher.ExpectConversion(EventName, TestUserId);
150+
151+
Thread.Sleep(1500);
152+
153+
Assert.True(eventDispatcher.CompareEvents());
154+
Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), "Exceeded timeout waiting for notification.");
155+
}
156+
157+
[Test]
158+
public void TestFlushOnMismatchProjectId()
159+
{
160+
var countdownEvent = new CountdownEvent(2);
161+
var eventDispatcher = new TestEventDispatcher(countdownEvent);
162+
SetEventProcessor(eventDispatcher);
163+
164+
Config.Revision = "1";
165+
Config.ProjectId = "X";
166+
var userEvent1 = BuildConversionEvent(EventName, Config);
167+
EventProcessor.Process(userEvent1);
168+
eventDispatcher.ExpectConversion(EventName, TestUserId);
169+
170+
Config.Revision = "1";
171+
Config.ProjectId = "Y";
172+
var userEvent2 = BuildConversionEvent(EventName, Config);
173+
EventProcessor.Process(userEvent2);
174+
eventDispatcher.ExpectConversion(EventName, TestUserId);
175+
176+
Thread.Sleep(1500);
177+
178+
Assert.True(eventDispatcher.CompareEvents());
179+
Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), "Exceeded timeout waiting for notification.");
180+
Assert.AreEqual(0, EventProcessor.EventQueue.Count);
181+
}
182+
183+
[Test]
184+
public void TestStopAndStart()
185+
{
186+
var countdownEvent = new CountdownEvent(2);
187+
var eventDispatcher = new TestEventDispatcher(countdownEvent);
188+
SetEventProcessor(eventDispatcher);
189+
190+
UserEvent userEvent = BuildConversionEvent(EventName);
191+
EventProcessor.Process(userEvent);
192+
eventDispatcher.ExpectConversion(EventName, TestUserId);
193+
Thread.Sleep(1500);
194+
Assert.True(eventDispatcher.CompareEvents());
195+
196+
EventProcessor.Stop();
197+
198+
EventProcessor.Process(userEvent);
199+
eventDispatcher.ExpectConversion(EventName, TestUserId);
200+
EventProcessor.Start();
201+
EventProcessor.Stop();
202+
203+
Assert.True(countdownEvent.Wait(TimeSpan.FromMilliseconds(MAX_DURATION_MS * 3)), "Exceeded timeout waiting for notification.");
204+
}
205+
206+
[Test]
207+
public void TestCloseTimeout()
208+
{
209+
var countdownEvent = new CountdownEvent(1);
210+
var eventDispatcher = new CountdownEventDispatcher { CountdownEvent = countdownEvent };
211+
SetEventProcessor(EventDispatcherMock.Object);
212+
213+
UserEvent userEvent = BuildConversionEvent(EventName);
214+
EventProcessor.Process(userEvent);
215+
EventProcessor.Stop();
216+
217+
countdownEvent.Signal();
218+
}
219+
220+
private void SetEventProcessor(IEventDispatcher eventDispatcher)
221+
{
222+
EventProcessor = new BatchEventProcessor.Builder()
223+
.WithEventQueue(eventQueue)
224+
.WithEventDispatcher(eventDispatcher)
225+
.WithMaxBatchSize(MAX_BATCH_SIZE)
226+
.WithFlushInterval(TimeSpan.FromMilliseconds(MAX_DURATION_MS))
227+
.WithTimeoutInterval(TimeSpan.FromMilliseconds(TIMEOUT_INTERVAL_MS))
228+
.WithLogger(LoggerMock.Object)
229+
.Build();
230+
}
231+
232+
private ConversionEvent BuildConversionEvent(string eventName)
233+
{
234+
return BuildConversionEvent(eventName, Config);
235+
}
236+
237+
private static ConversionEvent BuildConversionEvent(string eventName, ProjectConfig projectConfig)
238+
{
239+
return UserEventFactory.CreateConversionEvent(projectConfig, eventName, TestUserId,
240+
new UserAttributes(), new EventTags());
241+
}
242+
}
243+
244+
class CountdownEventDispatcher : IEventDispatcher
245+
{
246+
public ILogger Logger { get; set; }
247+
public CountdownEvent CountdownEvent { get; set; }
248+
public void DispatchEvent(LogEvent logEvent) => Assert.False(!CountdownEvent.Wait(TimeSpan.FromMilliseconds(BatchEventProcessorTest.TIMEOUT_INTERVAL_MS * 2)));
249+
}
250+
}

0 commit comments

Comments
 (0)