diff --git a/appveyor.yml b/appveyor.yml
index b5a69eab3e..88a4f72c54 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -25,7 +25,7 @@ cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
install:
- - choco install doxygen.portable codecov graphviz.portable
+ - choco install doxygen.portable codecov graphviz.portable opencover.portable
- nuget restore tgstation-server.sln
build:
project: tgstation-server.sln
@@ -33,29 +33,29 @@ build:
verbosity: minimal
publish_nuget: true
test_script:
+ - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Api.Tests*]*" -output:".\api_coverage.xml" -oldstyle
+ - ps: $wc = New-Object 'System.Net.WebClient'
+ - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Api.Tests\TestResults\results.trx))
+ - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Client.Tests*]*" -output:".\client_coverage.xml" -oldstyle
+ - ps: $wc = New-Object 'System.Net.WebClient'
+ - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Client.Tests\TestResults\results.trx))
+ - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Tests*]*" -output:".\host_coverage.xml" -oldstyle
+ - ps: $wc = New-Object 'System.Net.WebClient'
+ - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Tests\TestResults\results.trx))
+ - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Console.Tests*]*" -output:".\console_coverage.xml" -oldstyle
+ - ps: $wc = New-Object 'System.Net.WebClient'
+ - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Console.Tests\TestResults\results.trx))
- set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path%
- copy "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\appveyor.*" "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions" /y
- - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Api.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Api.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
- - ps: $wc = New-Object 'System.Net.WebClient'
- - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Client.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Client.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
- - ps: $wc = New-Object 'System.Net.WebClient'
- - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
- - ps: $wc = New-Object 'System.Net.WebClient'
- - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
- - ps: $wc = New-Object 'System.Net.WebClient'
- - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
- ps: $wc = New-Object 'System.Net.WebClient'
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Watchdog.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Watchdog.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
+ - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Watchdog.Tests*]*" -output:".\watchdog_coverage.xml" -oldstyle
- ps: $wc = New-Object 'System.Net.WebClient'
- - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
+ - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Watchdog.Tests\TestResults\results.trx))
+ - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]*" -output:".\server_coverage.xml" -oldstyle
- ps: $wc = New-Object 'System.Net.WebClient'
- - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
+ - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Tests\TestResults\results.trx))
after_test:
- ps: build/UploadCoverage.ps1
- ps: build/BuildDox.ps1
diff --git a/build/UploadCoverage.ps1 b/build/UploadCoverage.ps1
index 3d790646cd..61a0bd1069 100644
--- a/build/UploadCoverage.ps1
+++ b/build/UploadCoverage.ps1
@@ -1,10 +1,16 @@
-$coverageFilePath = Resolve-Path -path "TestResults\*\*.coverage"
-
-$coverageFilePath = $coverageFilePath.ToString()
+$coverageFilePaths = Get-ChildItem -Path TestResults -Filter *.coverage -Recurse -ErrorAction SilentlyContinue -Force | %{ $_.fullname }
+
+$coverageFilePathList = [string]$coverageFilePaths
Write-Host "Running CodeCoverage.exe..."
-&"C:\Program Files (x86)\Microsoft Visual Studio\2017\TestAgent\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe" analyze /output:coverage.coveragexml "$coverageFilePath"
+&"C:\Program Files (x86)\Microsoft Visual Studio\2017\TestAgent\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe" analyze /output:service.coveragexml "$coverageFilePathList"
rm -r TestResults
-codecov -f coverage.coveragexml
+codecov -f api_coverage.xml --flag unittests
+codecov -f client_coverage.xml --flag unittests
+codecov -f host_coverage.xml --flag unittests
+codecov -f console_coverage.xml --flag unittests
+codecov -f watchdog_coverage.xml --flag unittests
+codecov -f service.coveragexml --flag unittests
+codecov -f server_coverage.xml --flag integration
diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs
index 5f249a6330..2f9c59df69 100644
--- a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs
@@ -9,6 +9,11 @@ namespace Tgstation.Server.Host.Components.Repository
///
public interface IRepositoryManager : IDisposable
{
+ ///
+ /// If something is holding a lock on the repository
+ ///
+ bool InUse { get; }
+
///
/// If a operation is in progress
///
diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
index 7f3619e073..9b7ef5e95b 100644
--- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
@@ -11,6 +11,9 @@ namespace Tgstation.Server.Host.Components.Repository
///
sealed class RepositoryManager : IRepositoryManager
{
+ ///
+ public bool InUse => semaphore.CurrentCount == 0;
+
///
public bool CloneInProgress { get; private set; }
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
index f62bf504ba..90c7a4d218 100644
--- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
+++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
@@ -216,10 +216,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles
Path = configurationRelativePath
};
}
- catch (IOException e)
- {
- logger.LogDebug("IOException while reading {0}: {1}", path, e);
- }
catch (UnauthorizedAccessException)
{
//this happens on windows, dunno about linux
@@ -308,10 +304,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles
Path = configurationRelativePath
};
}
- catch (IOException e)
- {
- logger.LogDebug("IOException while writing {0}: {1}", path, e);
- }
catch (UnauthorizedAccessException)
{
//this happens on windows, dunno about linux
diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
index c362107fb5..34ded3ea45 100644
--- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
+using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
@@ -54,12 +55,23 @@ namespace Tgstation.Server.Host.Controllers
{
var newFile = await config.Write(model.Path, AuthenticationContext.SystemIdentity, model.Content, model.LastReadHash, cancellationToken).ConfigureAwait(false);
if (newFile == null)
- return Conflict();
+ return Conflict(new ErrorMessage
+ {
+ Message = ""
+ });
newFile.Content = null;
return model.LastReadHash == null ? (IActionResult)StatusCode((int)HttpStatusCode.Created, newFile) : Json(newFile);
}
+ catch(IOException e)
+ {
+ Logger.LogInformation("IOException while updating file {0}: {1}", model.Path, e);
+ return Conflict(new ErrorMessage
+ {
+ Message = e.Message
+ });
+ }
catch (NotImplementedException)
{
return StatusCode((int)HttpStatusCode.NotImplemented);
@@ -87,6 +99,14 @@ namespace Tgstation.Server.Host.Controllers
return Json(result);
}
+ catch (IOException e)
+ {
+ Logger.LogInformation("IOException while reading file {0}: {1}", filePath, e);
+ return Conflict(new ErrorMessage
+ {
+ Message = e.Message
+ });
+ }
catch (NotImplementedException)
{
return StatusCode((int)HttpStatusCode.NotImplemented);
diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
index 735bc19e6d..4de3cae653 100644
--- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
@@ -92,7 +92,7 @@ namespace Tgstation.Server.Host.Controllers
return needsDbUpdate;
}
- static async Task PopulateApi(Repository model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action revInfoSink, CancellationToken cancellationToken)
+ static async Task PopulateApi(Repository model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, CancellationToken cancellationToken)
{
model.IsGitHub = repository.IsGitHubRepository;
model.Origin = repository.Origin;
@@ -100,9 +100,8 @@ namespace Tgstation.Server.Host.Controllers
//rev info stuff
Models.RevisionInformation revisionInfo = null;
- var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, lastOriginCommitSha, x => revisionInfo = x, cancellationToken).ConfigureAwait(false);
+ var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, null, x => revisionInfo = x, cancellationToken).ConfigureAwait(false);
model.RevisionInformation = revisionInfo.ToApi();
- revInfoSink?.Invoke(revisionInfo);
return needsDbUpdate;
}
@@ -140,13 +139,25 @@ namespace Tgstation.Server.Host.Controllers
var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
if (repoManager.CloneInProgress)
- return Conflict();
+ return Conflict(new ErrorMessage
+ {
+ Message = "A clone operation is in progress!"
+ });
+
+ if(repoManager.InUse)
+ return Conflict(new ErrorMessage
+ {
+ Message = "The repo is busy!"
+ });
using (var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false))
{
if (repo != null)
//clone conflict
- return Conflict();
+ return Conflict(new ErrorMessage
+ {
+ Message = "The repository already exists!"
+ });
var job = new Models.Job
{
@@ -164,7 +175,7 @@ namespace Tgstation.Server.Host.Controllers
if (repos == null)
throw new JobException("Filesystem conflict while cloning repository!");
var db = serviceProvider.GetRequiredService();
- if (await PopulateApi(api, repos, db, Instance, null, null, cancellationToken).ConfigureAwait(false))
+ if (await PopulateApi(api, repos, db, Instance, cancellationToken).ConfigureAwait(false))
await db.Save(cancellationToken).ConfigureAwait(false);
}
}, cancellationToken).ConfigureAwait(false);
@@ -220,10 +231,23 @@ namespace Tgstation.Server.Host.Controllers
return StatusCode((int)HttpStatusCode.Gone);
var api = currentModel.ToApi();
+ var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
- using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
+ if (repoManager.CloneInProgress)
+ return Conflict(new ErrorMessage
+ {
+ Message = "A clone operation is in progress!"
+ });
+
+ if (repoManager.InUse)
+ return Conflict(new ErrorMessage
+ {
+ Message = "The repo is busy!"
+ });
+
+ using (var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false))
{
- if (repo != null && await PopulateApi(api, repo, DatabaseContext, Instance, null, null, cancellationToken).ConfigureAwait(false))
+ if (repo != null && await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false))
{
//user may have fucked with the repo without telling us, do what we can
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
@@ -301,6 +325,36 @@ namespace Tgstation.Server.Host.Controllers
currentModel.AccessToken = null;
}
+ var canRead = userRights.HasFlag(RepositoryRights.Read);
+
+ var api = canRead ? currentModel.ToApi() : new Repository();
+ var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
+
+ if (canRead)
+ {
+ if (repoManager.CloneInProgress)
+ return Conflict(new ErrorMessage
+ {
+ Message = "A clone operation is in progress!"
+ });
+
+ if (repoManager.InUse)
+ return Conflict(new ErrorMessage
+ {
+ Message = "The repo is busy!"
+ });
+
+ using (var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false))
+ {
+ if (repo == null)
+ return Conflict(new ErrorMessage
+ {
+ Message = "Repository could not be loaded!"
+ });
+ await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
//this is just db stuf so stow it away
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
@@ -315,7 +369,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) =>
{
- using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(ct).ConfigureAwait(false))
+ using (var repo = await repoManager.LoadRepository(ct).ConfigureAwait(false))
{
if (repo == null)
throw new JobException("Repository could not be loaded!");
@@ -598,7 +652,8 @@ namespace Tgstation.Server.Host.Controllers
}
}, cancellationToken).ConfigureAwait(false);
- return Accepted(job.ToApi());
+ api.ActiveJob = job.ToApi();
+ return Accepted(api);
}
}
}
diff --git a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs
index 9627a3e25e..d16b7ae628 100644
--- a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs
+++ b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs
@@ -52,11 +52,14 @@ namespace Tgstation.Server.Host.Core
///
public void Dispose()
{
- if (disposed)
- return;
- lockedSemaphore.Release();
+ lock (this)
+ {
+ if (disposed)
+ return;
+ disposed = true;
+ }
GC.SuppressFinalize(this);
- disposed = true;
+ lockedSemaphore.Release();
}
}
}
diff --git a/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs b/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs
new file mode 100644
index 0000000000..9b36d0a1a0
--- /dev/null
+++ b/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs
@@ -0,0 +1,17 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Net.Http.Headers;
+
+namespace Tgstation.Server.Client.Tests
+{
+ [TestClass]
+ public sealed class TestServerClientFactory
+ {
+ [TestMethod]
+ public void TestConstruction()
+ {
+ Assert.ThrowsException(() => new ServerClientFactory(null));
+ new ServerClientFactory(new ProductHeaderValue("Tgstation.Server.Client.Tests", GetType().Assembly.GetName().Version.ToString()));
+ }
+ }
+}