diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000000..b9a9d80675
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,17 @@
+codecov:
+ strict_yaml_branch: master
+coverage:
+ status:
+ project:
+ default:
+ threshold: 0
+ if_no_uploads: failure
+ if_ci_failed: failure
+ patch: off
+ changes:
+ default:
+ if_no_uploads: failure
+ if_ci_failed: failure
+ only_pulls: yes
+comment:
+ layout: "header, diff, changes"
diff --git a/.gitignore b/.gitignore
index 5dee86e7ae..90b3b65a9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ packages/*
*.user
*.dmb
*.int
+/TestResults
diff --git a/README.md b/README.md
index 3df7bca9b4..21bdb7331a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,14 @@
# Tgstation Toolkit:
+
+[](https://ci.appveyor.com/project/Cyberboss/tgstation-server-tools) [](https://travis-ci.org/tgstation/tgstation-server) [](https://codecov.io/gh/tgstation/tgstation-server)
+
+[](https://github.com/tgstation/tgstation-server/blob/master/LICENSE) [](http://isitmaintained.com/project/tgstation/tgstation-server "Average time to resolve an issue") [](https://badge.fury.io/nu/TGServiceInterface)
+
+[](http://forthebadge.com) [](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a)
+
+[](http://forthebadge.com) [](http://forthebadge.com)
+
+
This is a toolset to manage a production server of /tg/Station13 (and its forks). It includes the ability to update the server without having to stop or shutdown the server (the update will take effect next round) the ability start the server and restart it if it crashes, as well as systems for fixing errors and merging GitHub Pull Requests locally.
Generally, updates force a live tracking of the configured git repo, resetting local modifications. If you plan to make modifications, set up a new git repo to store your version of the code in, and point this script to that in the config (explained below). This can be on github or a local repo using file:/// urls.
diff --git a/TGServerService/DeprecatedInstanceConfig.cs b/TGServerService/DeprecatedInstanceConfig.cs
index 152962fdfa..0c201ae057 100644
--- a/TGServerService/DeprecatedInstanceConfig.cs
+++ b/TGServerService/DeprecatedInstanceConfig.cs
@@ -16,8 +16,8 @@ namespace TGServerService
///
/// Convert the settings version 6 .NET settings file to a config json
///
- /// An based off the old .NET setting file
- public static InstanceConfig CreateFromNETSettings()
+ /// An based off the old .NET setting file
+ public static IInstanceConfig CreateFromNETSettings()
{
var Config = Properties.Settings.Default;
var result = new DeprecatedInstanceConfig(LoadPreviousNetPropertyOrDefault("ServerDirectory", "C:\\tgstation-server-3"));
diff --git a/TGServerService/InstanceConfig.cs b/TGServerService/InstanceConfig.cs
index 3911ec69e7..69c6cf2ae7 100644
--- a/TGServerService/InstanceConfig.cs
+++ b/TGServerService/InstanceConfig.cs
@@ -4,122 +4,198 @@ using TGServiceInterface;
namespace TGServerService
{
- class InstanceConfig
+ ///
+ /// Configuration settings for a
+ ///
+ interface IInstanceConfig
+ {
+ ///
+ /// The directory this is for
+ ///
+ string Directory { get; }
+
+ ///
+ /// Actual version of the . Migrated up via
+ ///
+ ulong Version { get; }
+
+ ///
+ /// The name of the
+ ///
+ string Name { get; set; }
+
+ ///
+ /// If the is active
+ ///
+ bool Enabled { get; set; }
+
+ ///
+ /// The name of the .dme/.dmb the uses
+ ///
+ string ProjectName { get; set; }
+
+ ///
+ /// The port the runs on
+ ///
+ ushort Port { get; set; }
+
+ ///
+ /// The level for the
+ ///
+ DreamDaemonSecurity Security { get; set; }
+
+ ///
+ /// Whether or not the should immediately start DreamDaemon when activated
+ ///
+ bool Autostart { get; set; }
+
+ ///
+ /// Whether or not DreamDaemon allows connections from webclients
+ ///
+ bool Webclient { get; set; }
+
+ ///
+ /// Author and committer name for synchronize commits
+ ///
+ string CommitterName { get; set; }
+ ///
+ /// Author and committer e-mail for synchronize commits
+ ///
+ string CommitterEmail { get; set; }
+
+ ///
+ /// Encrypted serialized s
+ ///
+ string ChatProviderData { get; set; }
+
+ ///
+ /// Entropy for
+ ///
+ string ChatProviderEntropy { get; set; }
+
+ ///
+ /// If the should reattach to a running DreamDaemon
+ ///
+ bool ReattachRequired { get; set; }
+
+ ///
+ /// The of the runnning DreamDaemon
+ ///
+ int ReattachProcessID { get; set; }
+
+ ///
+ /// The port the runnning DreamDaemon was launched on
+ ///
+ ushort ReattachPort { get; set; }
+
+ ///
+ /// The serviceCommsKey the runnning DreamDaemon was launched on
+ ///
+ string ReattachCommsKey { get; set; }
+
+ ///
+ /// The API version of the runnning DreamDaemon
+ ///
+ string ReattachAPIVersion { get; set; }
+
+ ///
+ /// The user group allowed to use the
+ ///
+ string AuthorizedUserGroupSID { get; set; }
+
+ ///
+ /// The auto update interval for the
+ ///
+ ulong AutoUpdateInterval { get; set; }
+
+ ///
+ /// Whether or not testmerge commits are published to a temporary remote branch
+ ///
+ bool PushTestmergeCommits { get; set; }
+
+ ///
+ /// Saves the to it's
+ ///
+ void Save();
+ }
+
+ ///
+ class InstanceConfig : IInstanceConfig
{
///
/// The name the file is saved as in the
///
- //tell javascriptserializer to ignore these fields
[ScriptIgnore]
public const string JSONFilename = "Instance.json";
+
///
/// The current version of the config
///
[ScriptIgnore]
protected const ulong CurrentVersion = 0; //Literally any time you add/deprecated a field, this number needs to be bumped
- ///
- /// The directory this is for
- ///
+
+ ///
[ScriptIgnore]
public string Directory { get; private set; }
- ///
- /// Actual version of the . Migrated up via
- ///
+ ///
public ulong Version { get; protected set; } = CurrentVersion;
- ///
- /// The name of the
- ///
+ ///
public string Name { get; set; } = "TG Station Server";
- ///
- /// If the is active
- ///
+ ///
public bool Enabled { get; set; } = true;
- ///
- /// The name of the .dme/.dmb the uses
- ///
+ ///
public string ProjectName { get; set; } = "tgstation";
- ///
- /// The port the runs on
- ///
+ ///
public ushort Port { get; set; } = 1337;
- ///
- /// The level for the
- ///
+ ///
public DreamDaemonSecurity Security { get; set; } = DreamDaemonSecurity.Trusted;
- ///
- /// Whether or not the should immediately start DreamDaemon when activated
- ///
+ ///
public bool Autostart { get; set; } = false;
- ///
- /// Whether or not DreamDaemon allows connections from webclients
- ///
+ ///
public bool Webclient { get; set; } = false;
- ///
- /// Author and committer name for synchronize commits
- ///
+ ///
public string CommitterName { get; set; } = "tgstation-server";
- ///
- /// Author and committer e-mail for synchronize commits
- ///
+
+ ///
public string CommitterEmail { get; set; } = "tgstation-server@tgstation13.org";
- ///
- /// Encrypted serialized s
- ///
+ ///
public string ChatProviderData { get; set; } = ServerInstance.UninitializedString;
- ///
- /// Entropy for
- ///
+ ///
public string ChatProviderEntropy { get; set; }
- ///
- /// If the should reattach to a running DreamDaemon
- ///
+ ///
public bool ReattachRequired { get; set; } = false;
- ///
- /// The of the runnning DreamDaemon
- ///
+ ///
public int ReattachProcessID { get; set; }
- ///
- /// The port the runnning DreamDaemon was launched on
- ///
+ ///
public ushort ReattachPort { get; set; }
- ///
- /// The serviceCommsKey the runnning DreamDaemon was launched on
- ///
+ ///
public string ReattachCommsKey { get; set; }
- ///
- /// The API version of the runnning DreamDaemon
- ///
+ ///
public string ReattachAPIVersion { get; set; }
- ///
- /// The user group allowed to use the
- ///
+ ///
public string AuthorizedUserGroupSID { get; set; } = null;
- ///
- /// The auto update interval for the
- ///
+ ///
public ulong AutoUpdateInterval { get; set; } = 0;
- ///
- /// Whether or not testmerge commits are published to a temporary remote branch
- ///
+ ///
public bool PushTestmergeCommits { get; set; } = false;
///
@@ -131,9 +207,7 @@ namespace TGServerService
Directory = path;
}
- ///
- /// Saves the to it's
- ///
+ ///
public void Save()
{
var data = new JavaScriptSerializer().Serialize(this);
@@ -142,11 +216,11 @@ namespace TGServerService
}
///
- /// Loads and migrates an from a at
+ /// Loads and migrates an from a at
///
/// The path to the directory
- /// The migrated
- public static InstanceConfig Load(string path)
+ /// The migrated
+ public static IInstanceConfig Load(string path)
{
var configtext = File.ReadAllText(Path.Combine(path, JSONFilename));
var res = new JavaScriptSerializer().Deserialize(configtext);
diff --git a/TGServerService/ServerInstance/DreamDaemon.cs b/TGServerService/ServerInstance/DreamDaemon.cs
index 02e91cc34d..113797efe9 100644
--- a/TGServerService/ServerInstance/DreamDaemon.cs
+++ b/TGServerService/ServerInstance/DreamDaemon.cs
@@ -343,7 +343,7 @@ namespace TGServerService
var Now = DateTime.Now;
lock (watchdogLock)
{
- CurrentDDLog = String.Format("{0} {1} Diagnostics.txt", Now.ToLongDateString(), Now.ToLongTimeString()).Replace(':', '-');
+ CurrentDDLog = DateTime.UtcNow.ToString("yyyy-MM-ddTHH-mm-ssZ");
WriteCurrentDDLog("Starting monitoring...");
}
pcpu = new PerformanceCounter("Process", "% Processor Time", Proc.ProcessName, true);
diff --git a/TGServerService/ServerInstance/ServerInstance.cs b/TGServerService/ServerInstance/ServerInstance.cs
index e11f6b8459..ec239dcb80 100644
--- a/TGServerService/ServerInstance/ServerInstance.cs
+++ b/TGServerService/ServerInstance/ServerInstance.cs
@@ -24,11 +24,11 @@ namespace TGServerService
///
/// The configuration settings for the instance
///
- readonly InstanceConfig Config;
+ readonly IInstanceConfig Config;
///
/// Constructs and a
///
- public ServerInstance(InstanceConfig config, byte logID)
+ public ServerInstance(IInstanceConfig config, byte logID)
{
LoggingID = logID;
Config = config;
diff --git a/TGServerService/Service.cs b/TGServerService/Service.cs
index 13b1605af9..c4835b0a66 100644
--- a/TGServerService/Service.cs
+++ b/TGServerService/Service.cs
@@ -118,10 +118,10 @@ namespace TGServerService
}
///
- /// Enumerates configured s. Detaches those that fail to load
+ /// Enumerates configured s. Detaches those that fail to load
///
- /// Each configured
- IEnumerable GetInstanceConfigs()
+ /// Each configured
+ IEnumerable GetInstanceConfigs()
{
var pathsToRemove = new List();
lock (this)
@@ -129,7 +129,7 @@ namespace TGServerService
var IPS = Properties.Settings.Default.InstancePaths;
foreach (var I in IPS)
{
- InstanceConfig ic;
+ IInstanceConfig ic;
try
{
ic = InstanceConfig.Load(I);
@@ -319,9 +319,9 @@ namespace TGServerService
///
/// Creates and starts a for a at
///
- /// The for the
+ /// The for the
/// The inactive on success, on failure
- ServiceHost SetupInstance(InstanceConfig config)
+ ServiceHost SetupInstance(IInstanceConfig config)
{
ServerInstance instance;
string instanceName;
@@ -484,7 +484,7 @@ namespace TGServerService
foreach (var oic in GetInstanceConfigs())
if (Name == oic.Name)
return String.Format("Instance named {0} already exists!", oic.Name);
- InstanceConfig ic;
+ IInstanceConfig ic;
try
{
ic = new InstanceConfig(path)
@@ -506,9 +506,9 @@ namespace TGServerService
///
/// Starts and onlines an instance located at
///
- /// The for the
+ /// The for the
/// on success, error message on failure
- string SetupOneInstance(InstanceConfig config)
+ string SetupOneInstance(IInstanceConfig config)
{
try
{
@@ -536,7 +536,7 @@ namespace TGServerService
return String.Format("Instance at {0} already exists!", path);
if(!Directory.Exists(path))
return String.Format("There is no instance located at {0}!", path);
- InstanceConfig ic;
+ IInstanceConfig ic;
try
{
ic = InstanceConfig.Load(path);
@@ -635,7 +635,7 @@ namespace TGServerService
lock (this)
{
//we have to check em all anyway
- InstanceConfig the_droid_were_looking_for = null;
+ IInstanceConfig the_droid_were_looking_for = null;
foreach (var ic in GetInstanceConfigs())
if (ic.Name == name)
{
diff --git a/TGServiceInterface/ChatSetupInfo.cs b/TGServiceInterface/ChatSetupInfo.cs
index c34d6aea8e..20e522cba4 100644
--- a/TGServiceInterface/ChatSetupInfo.cs
+++ b/TGServiceInterface/ChatSetupInfo.cs
@@ -1,235 +1,235 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.Serialization;
-using System.Web.Script.Serialization;
-
-namespace TGServiceInterface
-{
- ///
- /// For setting up authentication no matter the chat provider
- ///
- [DataContract]
- [KnownType(typeof(IRCSetupInfo))]
- [KnownType(typeof(DiscordSetupInfo))]
- public class ChatSetupInfo
- {
- const int AdminListIndex = 0;
- const int AdminModeIndex = 1;
- const int AdminChannelIndex = 2;
- const int DevChannelIndex = 3;
- const int WDChannelIndex = 4;
- const int GameChannelIndex = 5;
- const int ProviderIndex = 6;
- const int EnabledIndex = 7;
- ///
- /// Starting index of which child classes should use to write their custom data to
- ///
- protected const int BaseIndex = 8;
- ///
- /// Set to if a child constructor should use the baseInfo parameter of to initialize it's property fields, otherwise
- ///
- protected readonly bool InitializeFields;
-
- ///
- /// Raw access to the underlying data
- ///
- [DataMember]
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Web.Script.Serialization;
+
+namespace TGServiceInterface
+{
+ ///
+ /// For setting up authentication no matter the chat provider
+ ///
+ [DataContract]
+ [KnownType(typeof(IRCSetupInfo))]
+ [KnownType(typeof(DiscordSetupInfo))]
+ public class ChatSetupInfo
+ {
+ const int AdminListIndex = 0;
+ const int AdminModeIndex = 1;
+ const int AdminChannelIndex = 2;
+ const int DevChannelIndex = 3;
+ const int WDChannelIndex = 4;
+ const int GameChannelIndex = 5;
+ const int ProviderIndex = 6;
+ const int EnabledIndex = 7;
+ ///
+ /// Starting index of which child classes should use to write their custom data to
+ ///
+ protected const int BaseIndex = 8;
+ ///
+ /// Set to if a child constructor should use the baseInfo parameter of to initialize it's property fields, otherwise
+ ///
+ protected readonly bool InitializeFields;
+
+ ///
+ /// Raw access to the underlying data
+ ///
+ [DataMember]
public IList DataFields { get; protected set; }
- ///
- /// Constructs a from optional
- ///
- /// The that this is for
- /// Optional past data
- /// The number of fields in this chat provider
- protected internal ChatSetupInfo(ChatProvider provider, ChatSetupInfo baseInfo, int numFields)
- {
- numFields += BaseIndex;
- InitializeFields = baseInfo == null || baseInfo.DataFields.Count != numFields;
-
- if (InitializeFields)
- {
- DataFields = new List(numFields);
- for (var I = 0; I < numFields; ++I)
- DataFields.Add(null);
-
- AdminList = new List();
- AdminChannels = new List();
- DevChannels = new List();
- GameChannels = new List();
- WatchdogChannels = new List();
- AdminsAreSpecial = false;
- Enabled = false;
- }
- else
- DataFields = baseInfo.DataFields;
- Provider = provider;
+ ///
+ /// Constructs a from optional
+ ///
+ /// The that this is for
+ /// Optional past data
+ /// The number of fields in this chat provider
+ protected internal ChatSetupInfo(ChatProvider provider, ChatSetupInfo baseInfo, int numFields)
+ {
+ numFields += BaseIndex;
+ InitializeFields = baseInfo == null || baseInfo.DataFields.Count != numFields;
+
+ if (InitializeFields)
+ {
+ DataFields = new List(numFields);
+ for (var I = 0; I < numFields; ++I)
+ DataFields.Add(null);
+
+ AdminList = new List();
+ AdminChannels = new List();
+ DevChannels = new List();
+ GameChannels = new List();
+ WatchdogChannels = new List();
+ AdminsAreSpecial = false;
+ Enabled = false;
+ }
+ else
+ DataFields = baseInfo.DataFields;
+ Provider = provider;
Specialize(true); //to check we have a valid provider
- }
-
- ///
- /// Recreates as the correct child
- ///
- /// If , is returned provided is a valid
- /// A new based on the type
- ChatSetupInfo Specialize(bool checkOnly)
- {
- switch (Provider)
- {
- case ChatProvider.IRC:
- if (!checkOnly)
- return new IRCSetupInfo(this);
- break;
- case ChatProvider.Discord:
+ }
+
+ ///
+ /// Recreates as the correct child
+ ///
+ /// If , is returned provided is a valid
+ /// A new based on the type
+ ChatSetupInfo Specialize(bool checkOnly)
+ {
+ switch (Provider)
+ {
+ case ChatProvider.IRC:
if (!checkOnly)
- return new DiscordSetupInfo(this);
- break;
- default:
- throw new Exception("Invalid provider!");
- }
- return null;
- }
-
- ///
- /// Properly formats a name for the
- ///
- /// The to format
- /// The formatted
- protected virtual string SanitizeChannelName(string channel)
- {
- return Specialize(false).SanitizeChannelName(channel);
- }
-
- ///
- /// Sanitizes a list of
- ///
- /// An of strings
- void SanitizeChannelNames(IList channelnames)
- {
- for (var I = 0; I < channelnames.Count; ++I)
-
- if (String.IsNullOrWhiteSpace(channelnames[I]))
- {
- channelnames.RemoveAt(I);
- --I;
- }
- else
- channelnames[I] = SanitizeChannelName(channelnames[I].Trim());
- }
-
- ///
- /// Constructs a from a data list
- ///
- /// The data
- public ChatSetupInfo(IList DeserializedData)
- {
- DataFields = DeserializedData;
- Specialize(false); //ensure provider type is valid
- }
- ///
- /// The list of admin entries
- ///
- public IList AdminList
- {
- get { return new JavaScriptSerializer().Deserialize>(DataFields[AdminListIndex]); }
- set { DataFields[AdminListIndex] = new JavaScriptSerializer().Serialize(value); }
- }
- ///
- /// If AdminList corresponds to a Provider specific recognization method
- ///
- public bool AdminsAreSpecial
- {
- get { return Convert.ToBoolean(DataFields[AdminModeIndex]); }
- set { DataFields[AdminModeIndex] = Convert.ToString(value); }
- }
- ///
- /// The channels from which admin commands/messages can be sent/received
- ///
- public IList AdminChannels
- {
- get { return new JavaScriptSerializer().Deserialize>(DataFields[AdminChannelIndex]); }
- set
- {
- SanitizeChannelNames(value);
- DataFields[AdminChannelIndex] = new JavaScriptSerializer().Serialize(value);
- }
- }
- ///
- /// The channels to which repo and compile messages are sent
- ///
- public IList DevChannels
- {
- get { return new JavaScriptSerializer().Deserialize>(DataFields[DevChannelIndex]); }
- set
- {
- SanitizeChannelNames(value);
- DataFields[DevChannelIndex] = new JavaScriptSerializer().Serialize(value);
- }
- }
- ///
- /// The channels to which watchdog messages are sent
- ///
- public IList WatchdogChannels
- {
- get { return new JavaScriptSerializer().Deserialize>(DataFields[WDChannelIndex]); }
- set
- {
- SanitizeChannelNames(value);
- DataFields[WDChannelIndex] = new JavaScriptSerializer().Serialize(value);
- }
- }
- ///
- /// The channels to which game messages are sent
- ///
- public IList GameChannels
- {
- get { return new JavaScriptSerializer().Deserialize>(DataFields[GameChannelIndex]); }
- set
- {
- SanitizeChannelNames(value);
- DataFields[GameChannelIndex] = new JavaScriptSerializer().Serialize(value);
- }
- }
- ///
- /// If this chat provider is enabled
- ///
- public bool Enabled
- {
- get { return Convert.ToBoolean(DataFields[EnabledIndex]); }
- set { DataFields[EnabledIndex] = Convert.ToString(value); }
- }
-
- ///
- /// The type of provider
- ///
- public ChatProvider Provider
- {
- get { return (ChatProvider)Convert.ToInt32(DataFields[ProviderIndex]); }
- set { DataFields[ProviderIndex] = Convert.ToString((int)value); }
- }
- }
-
- ///
- /// Chat provider for IRC. Admin entries should be user nicknames in normal mode or required channel flags in special mode
- ///
- [DataContract]
- public sealed class IRCSetupInfo : ChatSetupInfo
- {
- const int URLIndex = 0;
- const int PortIndex = 1;
- const int NickIndex = 2;
- const int AuthTargetIndex = 3;
- const int AuthMessageIndex = 4;
- const int AuthLevelIndex = 5;
+ return new IRCSetupInfo(this);
+ break;
+ case ChatProvider.Discord:
+ if (!checkOnly)
+ return new DiscordSetupInfo(this);
+ break;
+ default:
+ throw new Exception("Invalid provider!");
+ }
+ return null;
+ }
+
+ ///
+ /// Properly formats a name for the
+ ///
+ /// The to format
+ /// The formatted
+ protected virtual string SanitizeChannelName(string channel)
+ {
+ return Specialize(false).SanitizeChannelName(channel);
+ }
+
+ ///
+ /// Sanitizes a list of
+ ///
+ /// An of strings
+ void SanitizeChannelNames(IList channelnames)
+ {
+ for (var I = 0; I < channelnames.Count; ++I)
+
+ if (String.IsNullOrWhiteSpace(channelnames[I]))
+ {
+ channelnames.RemoveAt(I);
+ --I;
+ }
+ else
+ channelnames[I] = SanitizeChannelName(channelnames[I].Trim());
+ }
+
+ ///
+ /// Constructs a from a data list
+ ///
+ /// The data
+ public ChatSetupInfo(IList DeserializedData)
+ {
+ DataFields = DeserializedData;
+ Specialize(false); //ensure provider type is valid
+ }
+ ///
+ /// The list of admin entries
+ ///
+ public IList AdminList
+ {
+ get { return new JavaScriptSerializer().Deserialize>(DataFields[AdminListIndex]); }
+ set { DataFields[AdminListIndex] = new JavaScriptSerializer().Serialize(value); }
+ }
+ ///
+ /// If AdminList corresponds to a Provider specific recognization method
+ ///
+ public bool AdminsAreSpecial
+ {
+ get { return Convert.ToBoolean(DataFields[AdminModeIndex]); }
+ set { DataFields[AdminModeIndex] = Convert.ToString(value); }
+ }
+ ///
+ /// The channels from which admin commands/messages can be sent/received
+ ///
+ public IList AdminChannels
+ {
+ get { return new JavaScriptSerializer().Deserialize>(DataFields[AdminChannelIndex]); }
+ set
+ {
+ SanitizeChannelNames(value);
+ DataFields[AdminChannelIndex] = new JavaScriptSerializer().Serialize(value);
+ }
+ }
+ ///
+ /// The channels to which repo and compile messages are sent
+ ///
+ public IList DevChannels
+ {
+ get { return new JavaScriptSerializer().Deserialize>(DataFields[DevChannelIndex]); }
+ set
+ {
+ SanitizeChannelNames(value);
+ DataFields[DevChannelIndex] = new JavaScriptSerializer().Serialize(value);
+ }
+ }
+ ///
+ /// The channels to which watchdog messages are sent
+ ///
+ public IList WatchdogChannels
+ {
+ get { return new JavaScriptSerializer().Deserialize>(DataFields[WDChannelIndex]); }
+ set
+ {
+ SanitizeChannelNames(value);
+ DataFields[WDChannelIndex] = new JavaScriptSerializer().Serialize(value);
+ }
+ }
+ ///
+ /// The channels to which game messages are sent
+ ///
+ public IList GameChannels
+ {
+ get { return new JavaScriptSerializer().Deserialize>(DataFields[GameChannelIndex]); }
+ set
+ {
+ SanitizeChannelNames(value);
+ DataFields[GameChannelIndex] = new JavaScriptSerializer().Serialize(value);
+ }
+ }
+ ///
+ /// If this chat provider is enabled
+ ///
+ public bool Enabled
+ {
+ get { return Convert.ToBoolean(DataFields[EnabledIndex]); }
+ set { DataFields[EnabledIndex] = Convert.ToString(value); }
+ }
+
+ ///
+ /// The type of provider
+ ///
+ public ChatProvider Provider
+ {
+ get { return (ChatProvider)Convert.ToInt32(DataFields[ProviderIndex]); }
+ set { DataFields[ProviderIndex] = Convert.ToString((int)value); }
+ }
+ }
+
+ ///
+ /// Chat provider for IRC. Admin entries should be user nicknames in normal mode or required channel flags in special mode
+ ///
+ [DataContract]
+ public sealed class IRCSetupInfo : ChatSetupInfo
+ {
+ const int URLIndex = 0;
+ const int PortIndex = 1;
+ const int NickIndex = 2;
+ const int AuthTargetIndex = 3;
+ const int AuthMessageIndex = 4;
+ const int AuthLevelIndex = 5;
const int FieldsLen = 6;
- ///
- /// Construct IRC setup info from optional generic info. Defaults to TGS3 on rizons IRC server
- ///
- /// Optional generic info
- public IRCSetupInfo(ChatSetupInfo baseInfo = null) : base(ChatProvider.IRC, baseInfo, FieldsLen)
- {
- if (!InitializeFields)
+ ///
+ /// Construct IRC setup info from optional generic info. Defaults to TGS3 on rizons IRC server
+ ///
+ /// Optional generic info
+ public IRCSetupInfo(ChatSetupInfo baseInfo = null) : base(ChatProvider.IRC, baseInfo, FieldsLen)
+ {
+ if (!InitializeFields)
return;
Nickname = "TGS3";
@@ -239,106 +239,106 @@ namespace TGServiceInterface
AuthMessage = "";
AdminsAreSpecial = true;
AuthLevel = IRCMode.Op;
- }
-
- ///
- protected override string SanitizeChannelName(string working)
- {
- if (working[0] != '#')
- return "#" + working;
- return working;
- }
-
- ///
- /// The port of the IRC server
- ///
- public ushort Port
- {
- get { return Convert.ToUInt16(DataFields[BaseIndex + PortIndex]); }
- set { DataFields[BaseIndex + PortIndex] = value.ToString(); }
- }
- ///
- /// The URL of the IRC server
- ///
- public string URL
- {
- get { return DataFields[BaseIndex + URLIndex]; }
- set { DataFields[BaseIndex + URLIndex] = value; }
- }
- ///
- /// The nickname of the IRC bot
- ///
- public string Nickname
- {
- get { return DataFields[BaseIndex + NickIndex]; }
- set { DataFields[BaseIndex + NickIndex] = value; }
- }
- ///
- /// The target for sending authentication messages
- ///
- public string AuthTarget
- {
- get { return DataFields[BaseIndex + AuthTargetIndex]; }
- set { DataFields[BaseIndex + AuthTargetIndex] = value; }
- }
- ///
- /// The authentication message
- ///
- public string AuthMessage
- {
- get { return DataFields[BaseIndex + AuthMessageIndex]; }
- set { DataFields[BaseIndex + AuthMessageIndex] = value; }
- }
- ///
- /// The minimum mode required to use admin bot commands when in special auth mode
- ///
- public IRCMode AuthLevel
- {
- get { return (IRCMode)Convert.ToInt32(DataFields[BaseIndex + AuthLevelIndex]); }
- set { DataFields[BaseIndex + AuthLevelIndex] = Convert.ToString((int)value); }
- }
- }
-
- ///
- /// Chat provider for Discord. Admin entires should be user ids in normal mode or group ids in special mode
- ///
- [DataContract]
- public sealed class DiscordSetupInfo : ChatSetupInfo
- {
- const int BotTokenIndex = 0;
+ }
+
+ ///
+ protected override string SanitizeChannelName(string working)
+ {
+ if (working[0] != '#')
+ return "#" + working;
+ return working;
+ }
+
+ ///
+ /// The port of the IRC server
+ ///
+ public ushort Port
+ {
+ get { return Convert.ToUInt16(DataFields[BaseIndex + PortIndex]); }
+ set { DataFields[BaseIndex + PortIndex] = value.ToString(); }
+ }
+ ///
+ /// The URL of the IRC server
+ ///
+ public string URL
+ {
+ get { return DataFields[BaseIndex + URLIndex]; }
+ set { DataFields[BaseIndex + URLIndex] = value; }
+ }
+ ///
+ /// The nickname of the IRC bot
+ ///
+ public string Nickname
+ {
+ get { return DataFields[BaseIndex + NickIndex]; }
+ set { DataFields[BaseIndex + NickIndex] = value; }
+ }
+ ///
+ /// The target for sending authentication messages
+ ///
+ public string AuthTarget
+ {
+ get { return DataFields[BaseIndex + AuthTargetIndex]; }
+ set { DataFields[BaseIndex + AuthTargetIndex] = value; }
+ }
+ ///
+ /// The authentication message
+ ///
+ public string AuthMessage
+ {
+ get { return DataFields[BaseIndex + AuthMessageIndex]; }
+ set { DataFields[BaseIndex + AuthMessageIndex] = value; }
+ }
+ ///
+ /// The minimum mode required to use admin bot commands when in special auth mode
+ ///
+ public IRCMode AuthLevel
+ {
+ get { return (IRCMode)Convert.ToInt32(DataFields[BaseIndex + AuthLevelIndex]); }
+ set { DataFields[BaseIndex + AuthLevelIndex] = Convert.ToString((int)value); }
+ }
+ }
+
+ ///
+ /// Chat provider for Discord. Admin entires should be user ids in normal mode or group ids in special mode
+ ///
+ [DataContract]
+ public sealed class DiscordSetupInfo : ChatSetupInfo
+ {
+ const int BotTokenIndex = 0;
const int FieldsLen = 1;
- ///
- /// Construct Discord setup info from optional generic info. Default is not a valid discord bot tokent
- ///
- /// Optional generic info
- public DiscordSetupInfo(ChatSetupInfo baseInfo = null) : base(ChatProvider.Discord, baseInfo, FieldsLen)
- {
- if (!InitializeFields)
- return;
- BotToken = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; //needless to say, this is fake
- }
- ///
- protected override string SanitizeChannelName(string working)
- {
- working = working.Replace("<", "").Replace(">", "").Replace("&", ""); //filter out some stuff that can come in the copypasta
- try
- {
- Convert.ToUInt64(working);
- }
- catch
- {
- throw new Exception("Invalid Discord channel ID!");
- }
- return working;
- }
-
- ///
- /// The Discord bot token to use. See https://discordapp.com/developers/applications/me for registering bot accounts
- ///
- public string BotToken
- {
- get { return DataFields[BaseIndex + BotTokenIndex]; }
- set { DataFields[BaseIndex + BotTokenIndex] = value; }
- }
- }
-}
+ ///
+ /// Construct Discord setup info from optional generic info. Default is not a valid discord bot tokent
+ ///
+ /// Optional generic info
+ public DiscordSetupInfo(ChatSetupInfo baseInfo = null) : base(ChatProvider.Discord, baseInfo, FieldsLen)
+ {
+ if (!InitializeFields)
+ return;
+ BotToken = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; //needless to say, this is fake
+ }
+ ///
+ protected override string SanitizeChannelName(string working)
+ {
+ working = working.Replace("<", "").Replace(">", "").Replace("&", ""); //filter out some stuff that can come in the copypasta
+ try
+ {
+ Convert.ToUInt64(working);
+ }
+ catch
+ {
+ throw new Exception("Invalid Discord channel ID!");
+ }
+ return working;
+ }
+
+ ///
+ /// The Discord bot token to use. See https://discordapp.com/developers/applications/me for registering bot accounts
+ ///
+ public string BotToken
+ {
+ get { return DataFields[BaseIndex + BotTokenIndex]; }
+ set { DataFields[BaseIndex + BotTokenIndex] = value; }
+ }
+ }
+}
diff --git a/TGServiceTests/Service/TestInstanceConfig.cs b/TGServiceTests/Service/TestInstanceConfig.cs
index 3d49c9a24e..586ccd4990 100644
--- a/TGServiceTests/Service/TestInstanceConfig.cs
+++ b/TGServiceTests/Service/TestInstanceConfig.cs
@@ -19,7 +19,7 @@ namespace TGServerService.Tests
/// Creates a default at
///
///
- InstanceConfig CreateTempConfig()
+ IInstanceConfig CreateTempConfig()
{
return new InstanceConfig(TempPath);
}
diff --git a/TGServiceTests/TempDirectoryRequiredTest.cs b/TGServiceTests/TempDirectoryRequiredTest.cs
index dbf6aa4553..8a2dbbb4e7 100644
--- a/TGServiceTests/TempDirectoryRequiredTest.cs
+++ b/TGServiceTests/TempDirectoryRequiredTest.cs
@@ -6,6 +6,7 @@ namespace TGServiceTests
///
/// To be the parent of test classes that required a temporary directory
///
+ [TestClass]
public abstract class TempDirectoryRequiredTest
{
///
diff --git a/TGStationServer3.sln b/TGStationServer3.sln
index d0be8fd839..fd74f4e3f9 100644
--- a/TGStationServer3.sln
+++ b/TGStationServer3.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.27004.2006
+VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGServerService", "TGServerService\TGServerService.csproj", "{F32EDA25-0855-411C-AF5E-F0D042917E2D}"
EndProject
@@ -15,6 +15,7 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "TGServiceInstaller", "TGSer
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2D8FC6AA-1D33-44B6-81C2-35ED7A87EDBC}"
ProjectSection(SolutionItems) = preProject
+ .codecov.yml = .codecov.yml
.gitignore = .gitignore
.travis.yml = .travis.yml
appveyor.yml = appveyor.yml
@@ -74,11 +75,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{081BB0BB
ProjectSection(SolutionItems) = preProject
Tools\build_byond.sh = Tools\build_byond.sh
Tools\Config.dm = Tools\Config.dm
+ Tools\CoverageExclusions.runsettings = Tools\CoverageExclusions.runsettings
Tools\DMAPITravisTester.dme = Tools\DMAPITravisTester.dme
Tools\Doxyfile = Tools\Doxyfile
Tools\install_byond.sh = Tools\install_byond.sh
Tools\Test.dm = Tools\Test.dm
Tools\TGS3Build.ps1 = Tools\TGS3Build.ps1
+ Tools\UploadCoverage.ps1 = Tools\UploadCoverage.ps1
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{287B900C-1AFB-49B8-8BDD-C9058971C20C}"
diff --git a/Tools/CoverageExclusions.runsettings b/Tools/CoverageExclusions.runsettings
new file mode 100644
index 0000000000..92eee55060
--- /dev/null
+++ b/Tools/CoverageExclusions.runsettings
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ .*\\TGServiceTests\\.*
+
+
+
+
+ True
+ True
+ True
+ False
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tools/Doxyfile b/Tools/Doxyfile
index 2cab89ff8e..1f90072117 100644
--- a/Tools/Doxyfile
+++ b/Tools/Doxyfile
@@ -2206,7 +2206,7 @@ HIDE_UNDOC_RELATIONS = NO
# set to NO
# The default value is: NO.
-HAVE_DOT = YES
+# Generated in appveyor
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
# to run in parallel. When set to 0 doxygen will base this on the number of
diff --git a/Tools/TGS3Build.ps1 b/Tools/TGS3Build.ps1
index 911789d56e..8cf38bbc31 100644
--- a/Tools/TGS3Build.ps1
+++ b/Tools/TGS3Build.ps1
@@ -13,9 +13,11 @@ if($publish_dox){
echo "Cloning https://git@$github_url..."
git clone -b gh-pages --single-branch "https://git@$github_url" "$doxdir" 2>$null
rm -r "$doxdir\*"
+ Add-Content "$bf\Tools\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/tgs.ico`nHAVE_DOT=YES"
+}else{
+ Add-Content "$bf\Tools\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/tgs.ico"
}
-Add-Content "$bf\Tools\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/tgs.ico"
doxygen.exe "$bf\Tools\Doxyfile"
if($publish_dox){
diff --git a/Tools/UploadCoverage.ps1 b/Tools/UploadCoverage.ps1
new file mode 100644
index 0000000000..3d790646cd
--- /dev/null
+++ b/Tools/UploadCoverage.ps1
@@ -0,0 +1,10 @@
+$coverageFilePath = Resolve-Path -path "TestResults\*\*.coverage"
+
+$coverageFilePath = $coverageFilePath.ToString()
+
+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"
+
+rm -r TestResults
+
+codecov -f coverage.coveragexml
diff --git a/appveyor.yml b/appveyor.yml
index c1c67d718f..60ea867874 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -21,7 +21,7 @@ cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
install:
- - choco install fciv doxygen.portable graphviz.portable
+ - choco install fciv doxygen.portable graphviz.portable opencover.portable codecov
before_build:
- nuget restore TGStationServer3.sln
build:
@@ -33,6 +33,11 @@ after_build:
- ps: .\Tools\TGS3Build.ps1
- ps: if($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[TGSDeploy\]"){$env:TGSDeploy = "Do it."}
- ps: $env:TGSVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:APPVEYOR_BUILD_FOLDER/TGServerService/bin/Release/TGServerService.exe").FileVersion
+test_script:
+ - 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:Appveyor "TGServiceTests\bin\Release\TGServiceTests.dll" /Enablecodecoverage /Settings:"Tools/CoverageExclusions.runsettings" /inIsolation /Platform:x64
+ - powershell -Command "Tools/UploadCoverage.ps1"
deploy:
- provider: GitHub
release: "tgstation-server-v$(TGSVersion)"