Skip to content

Commit

Permalink
Merge pull request #183 from nunit/issue-168
Browse files Browse the repository at this point in the history
Add locking to AgentDatabase
  • Loading branch information
CharliePoole committed Mar 4, 2017
2 parents ebd2899 + 0159b91 commit 082e342
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 187 deletions.
170 changes: 170 additions & 0 deletions src/NUnitEngine/nunit.engine.tests/Services/AgentDataBaseTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// ***********************************************************************
// Copyright (c) 2017 Charlie Poole
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ***********************************************************************

using System;
using System.Collections.Generic;
using System.Threading;
using NUnit.Framework;

namespace NUnit.Engine.Services.Tests
{
public class AgentDataBaseTests
{
#pragma warning disable 414
private static int[] Counts = new int[] { 1, 3, 10 };
#pragma warning restore 414

const string COUNTS = nameof(Counts);

AgentDataBase _data;
List<Guid> _generatedGuids;

[SetUp]
public void SetUp()
{
_data = new AgentDataBase();
_generatedGuids = new List<Guid>();
}

[Test]
public void AddData()
{
AddRecords(5);

Assert.That(_generatedGuids.Count, Is.EqualTo(5));
Assert.That(_data.Count, Is.EqualTo(5));

var snap = _data.TakeSnapshot();
Assert.That(snap.Guids, Is.EqualTo(_generatedGuids));
}

[TestCaseSource(COUNTS)]
public void AddData_Parallel(int count)
{
GenerateGuids(count);
RunInParallel((g) => AddRecord((Guid)g));

Assert.That(_generatedGuids.Count, Is.EqualTo(count));
Assert.That(_data.Count, Is.EqualTo(count));

var snap = _data.TakeSnapshot();
Assert.That(snap.Guids, Is.EqualTo(_generatedGuids));
}

[Test]
public void ReadData()
{
AddRecords(5);

foreach (var guid in _generatedGuids)
{
var r = _data[guid];
Assert.NotNull(r);
Assert.That(r.Id, Is.EqualTo(guid));
}
}

[TestCaseSource(COUNTS)]
public void ReadData_Parallel(int count)
{
AddRecords(count);

RunInParallel((g) =>
{
var guid = (Guid)g;
var r = _data[guid];
Assert.NotNull(r);
Assert.That(r.Id, Is.EqualTo(guid));
});
}

[Test]
public void RemoveData()
{
AddRecords(5);

foreach (var guid in _generatedGuids)
_data.Remove(guid);

Assert.That(_data.Count, Is.EqualTo(0));
}

[TestCaseSource(COUNTS)]
public void RemoveData_Parallel(int count)
{
AddRecords(count);

RunInParallel((g) =>
{
var guid = (Guid)g;
_data.Remove(guid);
});

Assert.That(_data.Count, Is.EqualTo(0));
}

[Test]
public void ClearData()
{
AddRecords(5);

_data.Clear();

Assert.That(_data.Count, Is.EqualTo(0));
}

private void GenerateGuids(int count)
{
while (count-- > 0)
_generatedGuids.Add(Guid.NewGuid());
}

private void AddRecord(Guid guid)
{
_data.Add(new AgentRecord(guid, null, null, AgentStatus.Ready));
}

private void AddRecords(int count)
{
GenerateGuids(count);

foreach (var guid in _generatedGuids)
AddRecord(guid);
}

// Run in parallel for each generated guid
private void RunInParallel(ParameterizedThreadStart start)
{
var threads = new List<Thread>();

for (int i = 0; i < _generatedGuids.Count; i++)
threads.Add(new Thread(start));

for (int i = 0; i < _generatedGuids.Count; i++)
threads[i].Start(_generatedGuids[i]);

foreach (var thread in threads)
thread.Join();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
<Compile Include="Runners\TestEngineRunnerTests.cs" />
<Compile Include="Runners\ParallelTaskWorkerPoolTests.cs" />
<Compile Include="RuntimeFrameworkTests.cs" />
<Compile Include="Services\AgentDataBaseTests.cs" />
<Compile Include="Services\ExtensionAssemblyTests.cs" />
<Compile Include="Services\ExtensionServiceTests.cs" />
<Compile Include="Services\Fakes\FakeRuntimeService.cs" />
Expand Down
142 changes: 142 additions & 0 deletions src/NUnitEngine/nunit.engine/Services/AgentDataBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// ***********************************************************************
// Copyright (c) 2011-2016 Charlie Poole
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ***********************************************************************

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace NUnit.Engine.Services
{
internal class AgentRecord
{
public Guid Id;
public Process Process;
public ITestAgent Agent;
public AgentStatus Status;

public AgentRecord(Guid id, Process p, ITestAgent a, AgentStatus s)
{
this.Id = id;
this.Process = p;
this.Agent = a;
this.Status = s;
}

}

/// <summary>
/// A simple class that tracks data about this
/// agencies active and available agents.
/// This class is required to be multi-thread safe.
/// </summary>
internal class AgentDataBase
{
private readonly Dictionary<Guid, AgentRecord> _agentData = new Dictionary<Guid, AgentRecord>();
private readonly object _lock = new object();

public AgentRecord this[Guid id]
{
get
{
lock (_lock)
{
return _agentData[id];
}
}
}

public int Count
{
get
{
lock (_lock)
{
return _agentData.Count;
}
}
}

// Take a snapshot of the database - used primarily in testing.
public Snapshot TakeSnapshot()
{
lock (_lock)
{
return new Snapshot(_agentData.Values);
}
}

public void Add(AgentRecord r)
{
lock (_lock)
{
_agentData[r.Id] = r;
}
}

#region Methods not currently used

// These methods are not currently used, but are being
// maintained (and tested) for now since TestAgency is
// undergoing some changes and may need them again.

public void Remove(Guid agentId)
{
lock (_lock)
{
_agentData.Remove(agentId);
}
}

public void Clear()
{
lock (_lock)
{
_agentData.Clear();
}
}

#endregion

#region Nested Snapshot Class used in Testing

public class Snapshot
{
public Snapshot(IEnumerable<AgentRecord> data)
{
Guids = new List<Guid>();
Records = new List<AgentRecord>();

foreach(var record in data)
{
Guids.Add(record.Id);
Records.Add(record);
}
}

public ICollection<Guid> Guids { get; private set; }
public ICollection<AgentRecord> Records { get; private set; }
}

#endregion
}
}
Loading

0 comments on commit 082e342

Please sign in to comment.