Repository: NuGet/WebBackgrounder Branch: main Commit: aa52a997b902 Files: 77 Total size: 354.8 KB Directory structure: gitextract_2kl2wcg2/ ├── .gitignore ├── 3rdParty/ │ └── xunit/ │ ├── EULA.txt │ ├── HTML.xslt │ ├── NUnitXml.xslt │ ├── xunit.console.clr4.exe.config │ ├── xunit.console.clr4.x86.exe.config │ ├── xunit.console.exe.config │ ├── xunit.console.x86.exe.config │ ├── xunit.dll.tdnet │ ├── xunit.extensions.xml │ ├── xunit.runner.utility.xml │ └── xunit.xml ├── Build-Solution.ps1 ├── License.txt ├── README.md ├── WebBackgrounder.msbuild └── src/ ├── .nuget/ │ ├── NuGet.settings.targets │ └── NuGet.targets ├── WebBackgrounder/ │ ├── IJob.cs │ ├── IJobCoordinator.cs │ ├── IJobHost.cs │ ├── IWorkItem.cs │ ├── IWorkItemRepository.cs │ ├── Job.cs │ ├── JobHost.cs │ ├── JobManager.cs │ ├── JobUnitOfWork.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── Schedule.cs │ ├── Scheduler.cs │ ├── SingleServerJobCoordinator.cs │ ├── TimerExtensions.cs │ ├── WebBackgrounder.csproj │ ├── WebBackgrounder.nuspec │ ├── WebFarmJobCoordinator.cs │ └── WorkItemExtensions.cs ├── WebBackgrounder.DemoWeb/ │ ├── App_Start/ │ │ ├── EntityFramework.SqlServerCompact.cs │ │ └── WebBackgrounderSetup.cs │ ├── Content/ │ │ └── Site.css │ ├── Controllers/ │ │ └── HomeController.cs │ ├── ExceptionJob.cs │ ├── Global.asax │ ├── Global.asax.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── SampleJob.cs │ ├── Views/ │ │ ├── Home/ │ │ │ └── Index.cshtml │ │ ├── Shared/ │ │ │ ├── Error.cshtml │ │ │ └── _Layout.cshtml │ │ ├── Web.config │ │ └── _ViewStart.cshtml │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Web.config │ ├── WebBackgrounder.DemoWeb.csproj │ └── packages.config ├── WebBackgrounder.EntityFramework/ │ ├── Entities/ │ │ ├── IWorkItemsContext.cs │ │ ├── WorkItem.cs │ │ └── WorkItemsContext.cs │ ├── EntityWorkItemRepository.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── WebBackgrounder.EntityFramework.csproj │ ├── WebBackgrounder.EntityFramework.nuspec │ ├── WorkItemCleanupJob.cs │ └── packages.config ├── WebBackgrounder.UnitTests/ │ ├── EntityWorkItemRepositoryFacts.cs │ ├── InMemoryDbSet.cs │ ├── JobHostFacts.cs │ ├── JobManagerFacts.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── ScheduleFacts.cs │ ├── SchedulerFacts.cs │ ├── WebBackgrounder.UnitTests.csproj │ ├── WebFarmJobCoordinatorFacts.cs │ ├── WorkItemCleanupJobFacts.cs │ ├── WorkItemExtensionsFacts.cs │ └── packages.config └── WebBackgrounder.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ bin obj *.user *.suo App_Data *.results.xml /src/packages _ReSharper.* ================================================ FILE: 3rdParty/xunit/EULA.txt ================================================ This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 1. Definitions The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to the software. A "contributor" is any person that distributes its contribution under this license. "Licensed patents" are a contributor's patent claims that read directly on its contribution. 2. Grant of Rights (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 3. Conditions and Limitations (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. ================================================ FILE: 3rdParty/xunit/HTML.xslt ================================================ ]]> xUnit.net Test Results - <xsl:value-of select="@name"/>

Results for

Tests run:   Failures: , Skipped: , Run time: s

Failed tests


Failed fixtures


Skipped tests


All tests

Click test class name to expand/collapse test details

Results generated at
altrow s Skipped   :

Output

:
Stack Trace:

s ToggleClass('class') ToggleClass('class')    ( tests)

display: none; class
================================================ FILE: 3rdParty/xunit/NUnitXml.xslt ================================================ False True False True False True False True ================================================ FILE: 3rdParty/xunit/xunit.console.clr4.exe.config ================================================
================================================ FILE: 3rdParty/xunit/xunit.console.clr4.x86.exe.config ================================================
================================================ FILE: 3rdParty/xunit/xunit.console.exe.config ================================================
================================================ FILE: 3rdParty/xunit/xunit.console.x86.exe.config ================================================
================================================ FILE: 3rdParty/xunit/xunit.dll.tdnet ================================================ xUnit.net {0}.{1}.{2} build {3} xunit.runner.tdnet.dll Xunit.Runner.TdNet.TdNetRunner ================================================ FILE: 3rdParty/xunit/xunit.extensions.xml ================================================ xunit.extensions A wrapper for Assert which is used by . Verifies that a collection contains a given object. The type of the object to be verified The object expected to be in the collection The collection to be inspected Thrown when the object is not present in the collection Verifies that a collection contains a given object, using an equality comparer. The type of the object to be verified The object expected to be in the collection The collection to be inspected The comparer used to equate objects in the collection with the expected object Thrown when the object is not present in the collection Verifies that a string contains a given sub-string, using the current culture. The sub-string expected to be in the string The string to be inspected Thrown when the sub-string is not present inside the string Verifies that a string contains a given sub-string, using the given comparison type. The sub-string expected to be in the string The string to be inspected The type of string comparison to perform Thrown when the sub-string is not present inside the string Verifies that a collection does not contain a given object. The type of the object to be compared The object that is expected not to be in the collection The collection to be inspected Thrown when the object is present inside the container Verifies that a collection does not contain a given object, using an equality comparer. The type of the object to be compared The object that is expected not to be in the collection The collection to be inspected The comparer used to equate objects in the collection with the expected object Thrown when the object is present inside the container Verifies that a string does not contain a given sub-string, using the current culture. The sub-string which is expected not to be in the string The string to be inspected Thrown when the sub-string is present inside the string Verifies that a string does not contain a given sub-string, using the current culture. The sub-string which is expected not to be in the string The string to be inspected The type of string comparison to perform Thrown when the sub-string is present inside the given string Verifies that a block of code does not throw any exceptions. A delegate to the code to be tested Verifies that a collection is empty. The collection to be inspected Thrown when the collection is null Thrown when the collection is not empty Verifies that two objects are equal, using a default comparer. The type of the objects to be compared The expected value The value to be compared against Thrown when the objects are not equal Verifies that two objects are equal, using a custom equatable comparer. The type of the objects to be compared The expected value The value to be compared against The comparer used to compare the two objects Thrown when the objects are not equal Verifies that two values are equal, within the number of decimal places given by . The expected value The value to be compared against The number of decimal places (valid values: 0-15) Thrown when the values are not equal Verifies that two values are equal, within the number of decimal places given by . The expected value The value to be compared against The number of decimal places (valid values: 0-15) Thrown when the values are not equal Verifies that the condition is false. The condition to be tested Thrown if the condition is not false Verifies that the condition is false. The condition to be tested The message to show when the condition is not false Thrown if the condition is not false Verifies that a value is within a given range. The type of the value to be compared The actual value to be evaluated The (inclusive) low value of the range The (inclusive) high value of the range Thrown when the value is not in the given range Verifies that a value is within a given range, using a comparer. The type of the value to be compared The actual value to be evaluated The (inclusive) low value of the range The (inclusive) high value of the range The comparer used to evaluate the value's range Thrown when the value is not in the given range Verifies that an object is of the given type or a derived type. The type the object should be The object to be evaluated The object, casted to type T when successful Thrown when the object is not the given type Verifies that an object is of the given type or a derived type. The type the object should be The object to be evaluated Thrown when the object is not the given type Verifies that an object is not exactly the given type. The type the object should not be The object to be evaluated Thrown when the object is the given type Verifies that an object is not exactly the given type. The type the object should not be The object to be evaluated Thrown when the object is the given type Verifies that an object is exactly the given type (and not a derived type). The type the object should be The object to be evaluated The object, casted to type T when successful Thrown when the object is not the given type Verifies that an object is exactly the given type (and not a derived type). The type the object should be The object to be evaluated Thrown when the object is not the given type Verifies that a collection is not empty. The collection to be inspected Thrown when a null collection is passed Thrown when the collection is empty Verifies that two objects are not equal, using a default comparer. The type of the objects to be compared The expected object The actual object Thrown when the objects are equal Verifies that two objects are not equal, using a custom equality comparer. The type of the objects to be compared The expected object The actual object The comparer used to examine the objects Thrown when the objects are equal Verifies that a value is not within a given range, using the default comparer. The type of the value to be compared The actual value to be evaluated The (inclusive) low value of the range The (inclusive) high value of the range Thrown when the value is in the given range Verifies that a value is not within a given range, using a comparer. The type of the value to be compared The actual value to be evaluated The (inclusive) low value of the range The (inclusive) high value of the range The comparer used to evaluate the value's range Thrown when the value is in the given range Verifies that an object reference is not null. The object to be validated Thrown when the object is not null Verifies that two objects are not the same instance. The expected object instance The actual object instance Thrown when the objects are the same instance Verifies that an object reference is null. The object to be inspected Thrown when the object reference is not null Verifies that two objects are the same instance. The expected object instance The actual object instance Thrown when the objects are not the same instance Verifies that the given collection contains only a single element of the given type. The collection. The single item in the collection. Thrown when the collection does not contain exactly one element. Verifies that the given collection contains only a single element of the given type. The collection type. The collection. The single item in the collection. Thrown when the collection does not contain exactly one element. Verifies that the exact exception is thrown (and not a derived exception type). The type of the exception expected to be thrown A delegate to the code to be tested The exception that was thrown, when successful Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown Verifies that the exact exception is thrown (and not a derived exception type). Generally used to test property accessors. The type of the exception expected to be thrown A delegate to the code to be tested The exception that was thrown, when successful Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown Verifies that the exact exception is thrown (and not a derived exception type). The type of the exception expected to be thrown A delegate to the code to be tested The exception that was thrown, when successful Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown Verifies that the exact exception is thrown (and not a derived exception type). Generally used to test property accessors. The type of the exception expected to be thrown A delegate to the code to be tested The exception that was thrown, when successful Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown Verifies that an expression is true. The condition to be inspected Thrown when the condition is false Verifies that an expression is true. The condition to be inspected The message to be shown when the condition is false Thrown when the condition is false A class which can be derived from for test classes, which bring an overridable version of Assert (using the class. Gets a class which provides assertions. Apply this attribute to your test method to replace the with another role. Replaces the identity of the current thread with . The role's name Restores the original . The method under test Stores the current and replaces it with a new role identified in constructor. The method under test Gets the name. Apply this attribute to your test method to automatically create a that is rolled back when the test is finished. Rolls back the transaction. Creates the transaction. Gets or sets the isolation level of the transaction. Default value is .Unspecified. Gets or sets the scope option for the transaction. Default value is .Required. Gets or sets the timeout of the transaction, in milliseconds. By default, the transaction will not timeout. Provides a data source for a data theory, with the data coming from a class which must implement IEnumerable<object[]>. Abstract attribute which represents a data source for a data theory. Data source providers derive from this attribute and implement GetData to return the data for the theory. Returns the data to be used to test the theory. The parameter is provided so that the test data can be converted to the destination parameter type when necessary. Generally, data should NOT be automatically converted, UNLESS the source data format does not have rich types (for example, all numbers in Excel spreadsheets are returned as even if they are integers). Derivers of this class should NOT throw exceptions for mismatched types or mismatched number of parameters; the test framework will throw these exceptions at the correct time. The method that is being tested The types of the parameters for the test method The theory data Initializes a new instance of the class. The class that provides the data. Gets the type of the class that provides the data. Represents an implementation of which uses an instance of to get the data for a decorated test method. Converts a parameter to its destination parameter type, if necessary. The parameter value The destination parameter type (null if not known) The converted parameter value Gets the data adapter to be used to retrieve the test data. Provides a data source for a data theory, with the data coming from inline values. Initializes a new instance of the class. The data values to pass to the theory Returns the data to be used to test the theory. The method that is being tested The types of the parameters for the test method The theory data, in table form Gets the data values. Provides a data source for a data theory, with the data coming from an OLEDB connection. Creates a new instance of . The OLEDB connection string to the data The SELECT statement used to return the data for the theory Gets the connection string. Gets the select statement. Provides a data source for a data theory, with the data coming from a public static property on the test class. The property must return IEnumerable<object[]> with the test data. Creates a new instance of / The name of the public static property on the test class that will provide the test data Returns the data to be used to test the theory. The method that is being tested The types of the parameters for the test method The theory data, in table form Gets the property name. Provides a data source for a data theory, with the data coming a Microsoft SQL Server. Creates a new instance of , using a trusted connection. The server name of the Microsoft SQL Server The database name The SQL SELECT statement to return the data for the data theory Creates a new instance of , using the provided username and password. The server name of the Microsoft SQL Server The database name The username for the server The password for the server The SQL SELECT statement to return the data for the data theory Provides a data source for a data theory, with the data coming a Microsoft Excel (.xls) spreadsheet. Creates a new instance of . The filename of the XLS spreadsheet file; if the filename provided is relative, then it is relative to the location of xunit.extensions.dll. The SELECT statement that returns the data for the theory A wrapper around the static operations on which allows time to be frozen using the . The clock begins in the thawed state; that is, calls to , , and return current (non-frozen) values. Freezes the clock with the current time. Until is called, all calls to , , and will return the exact same values. Freezes the clock with the given date and time, considered to be local time. Until is called, all calls to , , and will return the exact same values. The local date and time to freeze to Freezes the clock with the given date and time, considered to be Coordinated Universal Time (UTC). Until is called, all calls to , , and will return the exact same values. The UTC date and time to freeze to Thaws the clock so that , , and return normal values. Gets a object that is set to the current date and time on this computer, expressed as the local time. Gets the current date. Gets a object that is set to the current date and time on this computer, expressed as the Coordinated Universal Time (UTC). Apply this attribute to your test method to freeze the time represented by the class. Freeze the clock with the current date and time. Freeze the clock with the given date, considered to be local time. The frozen year The frozen month The frozen day Freeze the clock with the given date and time, considered to be in local time. The frozen year The frozen month The frozen day The frozen hour The frozen minute The frozen second Freeze the clock with the given date and time, with the given kind of time. The frozen year The frozen month The frozen day The frozen hour The frozen minute The frozen second The frozen time kind Thaws the clock. The method under test Freezes the clock. The method under test Marks a test method as being a data theory. Data theories are tests which are fed various bits of data from a data source, mapping to parameters on the test method. If the data source contains multiple rows, then the test method is executed multiple times (once with each data row). Creates instances of which represent individual intended invocations of the test method, one per data row in the data source. The method under test An enumerator through the desired test method invocations Represents a single invocation of a data theory test method. Creates a new instance of . The method under test The parameters to be passed to the test method Gets the parameter values that are passed to the test method. Apply to a test method to trace the method begin and end. This method is called before the test method is executed. The method under test This method is called after the test method is executed. The method under test ================================================ FILE: 3rdParty/xunit/xunit.runner.utility.xml ================================================ xunit.runner.utility Guard class, used for guard clauses and argument validation Wraps calls to the Executor. Used by runners to perform version-resilient test enumeration and execution. Wraps calls to the Executor. Used by runners to perform version-resilient test enumeration and execution. Enumerates the tests in an assembly. The fully-formed assembly node of the XML Gets a count of the tests in the assembly. Returns the number of tests, if known; returns -1 if not known. May not represent an exact count, but should be a best effort guess by the framework. Runs all the tests in an assembly. The callback which is called as each test/class/assembly is finished, providing XML nodes that are part of the xUnit.net XML output format. Test runs can be cancelled by returning false to the callback. If null, there are no status callbacks (and cancellation isn't possible). Returns the fully-formed assembly node for the assembly that was just run. Runs all the tests in the given class. The type. The callback which is called as each test/class is finished, providing XML nodes that are part of the xUnit.net XML output format. Test runs can be cancelled by returning false to the callback. If null, there are no status callbacks (and cancellation isn't possible). Returns the fully-formed class node for the class that was just run. Runs a single test in a class. The type to run. The method to run. The callback which is called as each test/class is finished, providing XML nodes that are part of the xUnit.net XML output format. Test runs can be cancelled by returning false to the callback. If null, there are no status callbacks (and cancellation isn't possible). Returns the fully-formed class node for the class of the test that was just run. Runs several tests in a single class. The type. The methods to run. The callback which is called as each test/class is finished, providing XML nodes that are part of the xUnit.net XML output format. Test runs can be cancelled by returning false to the callback. If null, there are no status callbacks (and cancellation isn't possible). Returns the fully-formed class node for the class of the tests that were just run. Gets the full pathname to the assembly under test. Gets the full pathname to the configuration file. Gets the version of xunit.dll used by the test assembly. Initializes the class. Initializes a new instance of the class. The assembly filename. The config filename. If null, the default config filename will be used. Set to true to enable shadow copying; false, otherwise. Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. THIS CLASS IS FOR INTERNAL USE ONLY. THIS CLASS IS FOR INTERNAL USE ONLY. THIS CLASS IS FOR INTERNAL USE ONLY. THIS CLASS IS FOR INTERNAL USE ONLY. THIS CLASS IS FOR INTERNAL USE ONLY. THIS CLASS IS FOR INTERNAL USE ONLY. THIS CLASS IS FOR INTERNAL USE ONLY. The callback object which receives real-time status notifications from the test runner. Called when the assembly has finished running. The test assembly. The total number of tests run. The number of failed tests. The number of skipped tests. The time taken to run, in seconds. Called when the assembly has started running. The test assembly. Called when a class failure is encountered (i.e., when a fixture from IUseFixture throws an exception during construction or . The test class. The full type name of the exception. The exception message. The exception stack trace. Called when an exception is thrown (i.e., a catastrophic failure of the testing system). The test assembly. The exception that was thrown. Called when a test has finished running, regardless of what the result was. The test method. Return true to continue running tests; return false to stop the test run. Called when a test has started running. The test method. Return true to continue running tests; return false to stop the test run. Represents the ability to load and unload test assemblies, as well as enumerate the test assemblies, the test methods, and run tests. Represents the ability to enumerate and filter test methods. Enumerates all test methods. Enumerates test methods which pass the given filter. The test method filter. The test assemblies loaded into the environment. Enumerates the test assemblies in the environment. Enumerates the traits across all the loaded assemblies. Loads the specified assembly, using the default configuration file. The assembly filename. The which represents the newly loaded test assembly. Loads the specified assembly using the specified configuration file. The assembly filename. The config filename. The which represents the newly loaded test assembly. Loads the specified assembly using the specified configuration file. The assembly filename. The config filename. Whether the DLLs should be shadow copied. The which represents the newly loaded test assembly. Adds the assembly loaded into the given into the environment. The executor wrapper. The which represents the newly loaded test assembly. Runs the specified test methods. The test methods to run. The run status information callback. Returns the result as XML. Unloads the specified assembly. The assembly to unload. Represents a failed test run in the object model. Base class for all test results in the object model. Initializes a new instance of the class. The duration the test took to run. For skipped tests, should be 0.0. The display name of the test result. Gets the display name of the test result. Gets the duration the test took to run. Initializes a new instance of the class. The duration the test took to run. The display name of the test result. The output that was captured during the test run. Type of the exception. The exception message. The exception stack trace. Gets the output that was captured during the test run. Gets the type of the exception. Gets the exception message. Gets the exception stack trace. Represents a skipped test run in the object model. Initializes a new instance of the class. The display name of the test result. The skip reason. Gets the skip reason. Acts as an and adapts the callback messages into calls to an instance of . Represents a logger used by and . Called when the assembly has finished running. The assembly filename. The total number of tests run. The number of failed tests. The number of skipped tests. The time taken to run, in seconds. Called when the assembly has started running. The assembly filename. The configuration filename, if given; null, otherwise. The version of xunit.dll. Called when a class failure is encountered (i.e., when a fixture from IUseFixture throws an exception during construction or . The full type name of the class. The full type name of the exception. The exception message. The exception stack trace. Called when an exception is thrown (i.e., a catastrophic failure of the testing system). The assembly filename. The exception that was thrown. Called when a test fails. The description name of the test. The full type name of the test class. The name of the method. The time spent running the test, in seconds. The output of the test during its run. The full type name of the exception. The exception message. The exception stack trace. Called when a test has finished running, regardless of what the result was. The description name of the test. The full type name of the test class. The name of the method. Return true to continue running tests; return false to stop the test run. Called when a test has passed. The description name of the test. The full type name of the test class. The name of the method. The time spent running the test, in seconds. The output of the test during its run. Called when a test was finished. The description name of the test. The full type name of the test class. The name of the method. The skip reason. Called when a test has started running. The description name of the test. The full type name of the test class. The name of the method. Return true to continue running tests; return false to stop the test run. Initializes a new instance of the class. The test class. The run status information callback. Represents a passed test run in the object model. Initializes a new instance of the class. The duration the test took to run. The display name of the test result. The output that was captured during the test run. Gets the output that was captured during the test run. Indicates the composite test method status The method has not been run All test results for the last run passed At least one test result for the last run failed At least one test result for the last run was skipped, and none failed Represents a set of filters for an . Initializes a new instance of the class. Filters the given methods using the defined filter values. The methods to filter. The filtered methods. Gets the set of trait filters for tests to exclude. Gets the set of trait filters for tests to include. Represents an xUnit Test Project file (.xunit file) Initializes a new instance of the class. Adds an assembly to the project The assembly to be added Loads an xUnit.net Test Project file from disk. The test project filename Removes assembly from the assembly list The assembly to be removed Saves the xUnit.net Test Project file to disk using the project's filename. Saves the xUnit.net Test Project file to disk using the provided filename. The projects filename is updated to match this new name. The test project filename Gets or sets the assemblies in the project. Gets or set the filename of the project. Gets the filters applied to this project. Gets or sets a flag which indicates if this project has been modified since the last time it was loaded or saved. Represents an assembly in an . Initializes a new instance of the class. Gets or sets the assembly filename. Gets or sets the config filename. Gets or sets a value indicating whether to shadow copy the assembly when running the tests. The xUnit.net GUI runner does not support this field. Gets or sets the output filenames. The dictionary key is the type of the file to be output; the dictionary value is the filename to write the output to. The xUnit.net GUI runner does not support this field. The MSBuild runner only supports output of type 'xml', 'html', and 'nunit'. Interface which represents a high level test runner. Executes the tests in the assembly. Returns true if there were no failures; return false otherwise. Executes the tests in the assembly, and then executes the transforms with the resulting assembly XML. The transforms to execute. Returns true if there were no failures; return false otherwise. Runs the class. The type. Runs a single test in a test class. The full name of the class. The name of the method. Runs the list of tests in a test class. The full name of the class. The names of the methods to run. Represents a single test assembly with test classes. Initializes a new instance of the class. The executor wrapper. The test classes. Enumerates the test classes in the assembly. Runs the specified test methods. The test methods to run. The run status information callback. Returns the result as XML. Gets the assembly filename. Gets the config filename. Gets the executor wrapper. Gets the version of xunit.dll that the tests are linked against. Represents a single class with test methods. Initializes a new instance of the class. The namespace-qualified type name that this class represents. The test methods inside this test class. Runs the specified test methods. The test methods to run. The run status information callback. Returns the result as XML. Runs the specified tests in the given type, calling the callback as appropriate. This override point exists primarily for unit testing purposes. The test methods to run The run status information callback. Gets the test assembly that this class belongs to. Gets the namespace-qualified type name of this class. Represents a single test method. Initializes a new instance of the class. The method name. The method's display name. The method's traits. Gets the method's display name. Gets the method's name. Gets the run results for the last run. Gets the composite run status for all the results of the last run. Gets the test class this test method belongs to. Gets the method's traits. The result of a test run via . All tests passed, with no class-level failures At least one test failed, or there was a class-level failure There were no tests to run Represents a transformation of the resulting assembly XML into some output format. Transforms the given assembly XML into the destination format. The assembly XML. Gets the output filename, if known; returns null if the output isn't done to file. Runs tests in an assembly, and transforms the XML results into calls to the provided . Initializes a new instance of the class. The executor wrapper. The logger. An implementation of which writes the XML to a file without any transformation applied. Initializes a new instance of the class. The output filename. An implementation of which writes the XML to a file after applying the XSL stylesheet in the given stream. Initializes a new instance of the class. The XSL filename. The output filename. Initializes a new instance of the class. The stream with the XSL stylesheet. The output filename. Gets or sets the XSL filename. Gets or sets the XSL stream. A dictionary which contains multiple unique values for each key. The type of the key. The type of the value. Adds the value for the given key. If the key does not exist in the dictionary yet, it will add it. The key. The value. Removes all keys and values from the dictionary. Determines whether the dictionary contains to specified key and value. The key. The value. Calls the delegate once for each key/value pair in the dictionary. Removes the given key and all of its values. Removes the given value from the given key. If this was the last value for the key, then the key is removed as well. The key. The value. Gets the values for the given key. Gets the count of the keys in the dictionary. Gets the keys. Responsible for building instances. Uses an instance of to interrogate the list of available tests and create the entire object model tree. Creates a which is a complete object model over the tests inside of instance of . The executor wrapper The fully populated object model Parses the XML nodes from the version resilient runner facility and converts them into calls against the provided . Logs a result XML node. Maybe be any kind of XML node. The node to be logged. The logger. Returns true if the user wishes to continue running tests; returns false otherwise. Logs the assembly node by calling . The assembly node. The logger. Logs the class node by calling (if the class failed). The exception type was added in xUnit.net 1.1, so when the test assembly is linked against xUnit.net versions prior to 1.1, the exception type will be null. The class node. The logger. Returns true if the user wishes to continue running tests; returns false otherwise. Logs the start node by calling . The start node was added in xUnit.net 1.1, so it will only be present when the test assembly is linked against xunit.dll version 1.1 or later. The start node. The logger. Returns true if the user wishes to continue running tests; returns false otherwise. Logs the test node by calling . It will also call , , or as appropriate. The test node. The logger. Returns true if the user wishes to continue running tests; returns false otherwise. ================================================ FILE: 3rdParty/xunit/xunit.xml ================================================ xunit Contains various static methods that are used to verify that conditions are met during the process of running tests. Initializes a new instance of the class. Verifies that a collection contains a given object. The type of the object to be verified The object expected to be in the collection The collection to be inspected Thrown when the object is not present in the collection Verifies that a collection contains a given object, using an equality comparer. The type of the object to be verified The object expected to be in the collection The collection to be inspected The comparer used to equate objects in the collection with the expected object Thrown when the object is not present in the collection Verifies that a string contains a given sub-string, using the current culture. The sub-string expected to be in the string The string to be inspected Thrown when the sub-string is not present inside the string Verifies that a string contains a given sub-string, using the given comparison type. The sub-string expected to be in the string The string to be inspected The type of string comparison to perform Thrown when the sub-string is not present inside the string Verifies that a collection does not contain a given object. The type of the object to be compared The object that is expected not to be in the collection The collection to be inspected Thrown when the object is present inside the container Verifies that a collection does not contain a given object, using an equality comparer. The type of the object to be compared The object that is expected not to be in the collection The collection to be inspected The comparer used to equate objects in the collection with the expected object Thrown when the object is present inside the container Verifies that a string does not contain a given sub-string, using the current culture. The sub-string which is expected not to be in the string The string to be inspected Thrown when the sub-string is present inside the string Verifies that a string does not contain a given sub-string, using the current culture. The sub-string which is expected not to be in the string The string to be inspected The type of string comparison to perform Thrown when the sub-string is present inside the given string Verifies that a block of code does not throw any exceptions. A delegate to the code to be tested Verifies that a collection is empty. The collection to be inspected Thrown when the collection is null Thrown when the collection is not empty Verifies that two objects are equal, using a default comparer. The type of the objects to be compared The expected value The value to be compared against Thrown when the objects are not equal Verifies that two objects are equal, using a custom equatable comparer. The type of the objects to be compared The expected value The value to be compared against The comparer used to compare the two objects Thrown when the objects are not equal Verifies that two values are equal, within the number of decimal places given by . The expected value The value to be compared against The number of decimal places (valid values: 0-15) Thrown when the values are not equal Verifies that two values are equal, within the number of decimal places given by . The expected value The value to be compared against The number of decimal places (valid values: 0-15) Thrown when the values are not equal Do not call this method. Verifies that the condition is false. The condition to be tested Thrown if the condition is not false Verifies that the condition is false. The condition to be tested The message to show when the condition is not false Thrown if the condition is not false Verifies that a value is within a given range. The type of the value to be compared The actual value to be evaluated The (inclusive) low value of the range The (inclusive) high value of the range Thrown when the value is not in the given range Verifies that a value is within a given range, using a comparer. The type of the value to be compared The actual value to be evaluated The (inclusive) low value of the range The (inclusive) high value of the range The comparer used to evaluate the value's range Thrown when the value is not in the given range Verifies that an object is of the given type or a derived type. The type the object should be The object to be evaluated The object, casted to type T when successful Thrown when the object is not the given type Verifies that an object is of the given type or a derived type. The type the object should be The object to be evaluated Thrown when the object is not the given type Verifies that an object is not exactly the given type. The type the object should not be The object to be evaluated Thrown when the object is the given type Verifies that an object is not exactly the given type. The type the object should not be The object to be evaluated Thrown when the object is the given type Verifies that an object is exactly the given type (and not a derived type). The type the object should be The object to be evaluated The object, casted to type T when successful Thrown when the object is not the given type Verifies that an object is exactly the given type (and not a derived type). The type the object should be The object to be evaluated Thrown when the object is not the given type Verifies that a collection is not empty. The collection to be inspected Thrown when a null collection is passed Thrown when the collection is empty Verifies that two objects are not equal, using a default comparer. The type of the objects to be compared The expected object The actual object Thrown when the objects are equal Verifies that two objects are not equal, using a custom equality comparer. The type of the objects to be compared The expected object The actual object The comparer used to examine the objects Thrown when the objects are equal Verifies that a value is not within a given range, using the default comparer. The type of the value to be compared The actual value to be evaluated The (inclusive) low value of the range The (inclusive) high value of the range Thrown when the value is in the given range Verifies that a value is not within a given range, using a comparer. The type of the value to be compared The actual value to be evaluated The (inclusive) low value of the range The (inclusive) high value of the range The comparer used to evaluate the value's range Thrown when the value is in the given range Verifies that an object reference is not null. The object to be validated Thrown when the object is not null Verifies that two objects are not the same instance. The expected object instance The actual object instance Thrown when the objects are the same instance Verifies that an object reference is null. The object to be inspected Thrown when the object reference is not null Verifies that the provided object raised INotifyPropertyChanged.PropertyChanged as a result of executing the given test code. The object which should raise the notification The property name for which the notification should be raised The test code which should cause the notification to be raised Thrown when the notification is not raised Verifies that two objects are the same instance. The expected object instance The actual object instance Thrown when the objects are not the same instance Verifies that the given collection contains only a single element of the given type. The collection. The single item in the collection. Thrown when the collection does not contain exactly one element. Verifies that the given collection contains only a single element of the given value. The collection may or may not contain other values. The collection. The value to find in the collection. The single item in the collection. Thrown when the collection does not contain exactly one element. Verifies that the given collection contains only a single element of the given type. The collection type. The collection. The single item in the collection. Thrown when the collection does not contain exactly one element. Verifies that the given collection contains only a single element of the given type which matches the given predicate. The collection may or may not contain other values which do not match the given predicate. The collection type. The collection. The item matching predicate. The single item in the filtered collection. Thrown when the filtered collection does not contain exactly one element. Verifies that the exact exception is thrown (and not a derived exception type). The type of the exception expected to be thrown A delegate to the code to be tested The exception that was thrown, when successful Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown Verifies that the exact exception is thrown (and not a derived exception type). Generally used to test property accessors. The type of the exception expected to be thrown A delegate to the code to be tested The exception that was thrown, when successful Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown Verifies that the exact exception is thrown (and not a derived exception type). The type of the exception expected to be thrown A delegate to the code to be tested The exception that was thrown, when successful Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown Verifies that the exact exception is thrown (and not a derived exception type). Generally used to test property accessors. The type of the exception expected to be thrown A delegate to the code to be tested The exception that was thrown, when successful Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown Verifies that an expression is true. The condition to be inspected Thrown when the condition is false Verifies that an expression is true. The condition to be inspected The message to be shown when the condition is false Thrown when the condition is false Used by the PropertyChanged. Used by the Throws and DoesNotThrow methods. Used by the Throws and DoesNotThrow methods. This command sets up the necessary trace listeners and standard output/error listeners to capture Assert/Debug.Trace failures, output to stdout/stderr, and Assert/Debug.Write text. It also captures any exceptions that are thrown and packages them as FailedResults, including the possibility that the configuration file is messed up (which is exposed when we attempt to manipulate the trace listener list). Base class used by commands which delegate to inner commands. Interface which represents the ability to invoke of a test method. Executes the test method. The instance of the test class Returns information about the test run Creates the start XML to be sent to the callback when the test is about to start running. Return the of the start node, or null if the test is known that it will not be running. Gets the display name of the test method. Determines if the test runner infrastructure should create a new instance of the test class before running the test. Determines if the test should be limited to running a specific amount of time before automatically failing. The timeout value, in milliseconds; if zero, the test will not have a timeout. Creates a new instance of the class. The inner command to delegate to. Initializes a new instance of the class. The command that will be wrapped. The test method. Represents an implementation of to be used with tests which are decorated with the . Represents an xUnit.net test command. The method under test. Initializes a new instance of the class. The method under test. The display name of the test. The timeout, in milliseconds. Gets the name of the method under test. Gets the name of the type under test. Initializes a new instance of the class. The test method. Base class for exceptions that have actual and expected values The base assert exception class Initializes a new instance of the class. Initializes a new instance of the class. The user message to be displayed Initializes a new instance of the class. The user message to be displayed The inner exception Initializes a new instance of the class. The user message to be displayed The stack trace to be displayed Filters the stack trace to remove all lines that occur within the testing framework. The original stack trace The filtered stack trace Gets a string representation of the frames on the call stack at the time the current exception was thrown. A string that describes the contents of the call stack, with the most recent method call appearing first. Gets the user message Creates a new instance of the class. The expected value The actual value The user message to be shown Creates a new instance of the class. The expected value The actual value The user message to be shown Set to true to skip the check for difference position Gets the actual value. Gets the expected value. Gets a message that describes the current exception. Includes the expected and actual values. The error message that explains the reason for the exception, or an empty string(""). 1 Exception thrown when a collection unexpectedly does not contain the expected value. Creates a new instance of the class. The expected object value Creates a new instance of the class. The expected object value The actual value Exception to be thrown from when the number of parameter values does not the test method signature. Exception thrown when code unexpectedly fails change a property. Creates a new instance of the class. Call this constructor when no exception was thrown. The type of the exception that was expected Exception thrown when the collection did not contain exactly one element. Initializes a new instance of the class. The numbers of items in the collection. Internal class used for version-resilient test runners. DO NOT CALL DIRECTLY. Version-resilient runners should link against xunit.runner.utility.dll and use ExecutorWrapper instead. Exception thrown when the value is unexpectedly not of the given type or a derived type. Creates a new instance of the class. The expected type The actual object value Allows the user to record actions for a test. Records any exception which is thrown by the given code. The code which may thrown an exception. Returns the exception that was thrown by the code; null, otherwise. Records any exception which is thrown by the given code that has a return value. Generally used for testing property accessors. The code which may thrown an exception. Returns the exception that was thrown by the code; null, otherwise. Exception that is thrown when one or more exceptions are thrown from the After method of a . Initializes a new instance of the class. The exceptions. Initializes a new instance of the class. The exceptions. Gets the list of exceptions thrown in the After method. Gets a message that describes the current exception. Gets a string representation of the frames on the call stack at the time the current exception was thrown. Implementation of which executes the instances attached to a test method. Initializes a new instance of the class. The inner command. The method. Executes the test method. The instance of the test class Returns information about the test run This class supports the xUnit.net infrastructure and is not intended to be used directly from your code. This API supports the xUnit.net infrastructure and is not intended to be used directly from your code. This API supports the xUnit.net infrastructure and is not intended to be used directly from your code. This API supports the xUnit.net infrastructure and is not intended to be used directly from your code. Guard class, used for guard clauses and argument validation Base class which contains XML manipulation helper methods Interface that represents a single test result. Converts the test result into XML that is consumed by the test runners. The parent node. The newly created XML node. The amount of time spent in execution Adds the test execution time to the XML node. The XML node. Utility methods for dealing with exceptions. Gets the message for the exception, including any inner exception messages. The exception The formatted message Gets the stack trace for the exception, including any inner exceptions. The exception The formatted stack trace Rethrows an exception object without losing the existing stack trace information The exception to re-throw. For more information on this technique, see http://www.dotnetjunkies.com/WebLog/chris.taylor/archive/2004/03/03/8353.aspx A dictionary which contains multiple unique values for each key. The type of the key. The type of the value. Adds the value for the given key. If the key does not exist in the dictionary yet, it will add it. The key. The value. Removes all keys and values from the dictionary. Determines whether the dictionary contains to specified key and value. The key. The value. Calls the delegate once for each key/value pair in the dictionary. Removes the given key and all of its values. Removes the given value from the given key. If this was the last value for the key, then the key is removed as well. The key. The value. Gets the values for the given key. Gets the count of the keys in the dictionary. Gets the keys. XML utility methods Adds an attribute to an XML node. The XML node. The attribute name. The attribute value. Adds a child element to an XML node. The parent XML node. The child element name. The new child XML element. Exception that is thrown when a call to Debug.Assert() fails. Creates a new instance of the class. The original assert message Creates a new instance of the class. The original assert message The original assert detailed message Gets the original assert detailed message. Gets the original assert message. Gets a message that describes the current exception. Exception thrown when a collection unexpectedly contains the expected value. Creates a new instance of the class. The expected object value Exception thrown when code unexpectedly throws an exception. Creates a new instance of the class. Actual exception Gets a string representation of the frames on the call stack at the time the current exception was thrown. A string that describes the contents of the call stack, with the most recent method call appearing first. Exception thrown when a collection is unexpectedly not empty. Creates a new instance of the class. Exception thrown when two values are unexpectedly not equal. Creates a new instance of the class. The expected object value The actual object value Creates a new instance of the class. The expected object value The actual object value Set to true to skip the check for difference position Exception thrown when a value is unexpectedly true. Creates a new instance of the class. The user message to be display, or null for the default message Exception thrown when a value is unexpectedly not in the given range. Creates a new instance of the class. The actual object value The low value of the range The high value of the range Gets the actual object value Gets the high value of the range Gets the low value of the range Gets a message that describes the current exception. The error message that explains the reason for the exception, or an empty string(""). Exception thrown when the value is unexpectedly of the exact given type. Creates a new instance of the class. The expected type The actual object value Exception thrown when the value is unexpectedly not of the exact given type. Creates a new instance of the class. The expected type The actual object value Used to decorate xUnit.net test classes that utilize fixture classes. An instance of the fixture data is initialized just before the first test in the class is run, and if it implements IDisposable, is disposed after the last test in the class is run. The type of the fixture Called on the test class just before each test method is run, passing the fixture data so that it can be used for the test. All test runs share the same instance of fixture data. The fixture data Exception thrown when a value is unexpectedly in the given range. Creates a new instance of the class. The actual object value The low value of the range The high value of the range Gets the actual object value Gets the high value of the range Gets the low value of the range Gets a message that describes the current exception. The error message that explains the reason for the exception, or an empty string(""). Base attribute which indicates a test method interception (allows code to be run before and after the test is run). This method is called after the test method is executed. The method under test This method is called before the test method is executed. The method under test Exception thrown when a collection is unexpectedly empty. Creates a new instance of the class. Exception thrown when two values are unexpectedly equal. Creates a new instance of the class. Exception thrown when an object is unexpectedly null. Creates a new instance of the class. Exception thrown when two values are unexpected the same instance. Creates a new instance of the class. Exception thrown when an object reference is unexpectedly not null. Creates a new instance of the class. Command that automatically creates the instance of the test class and disposes it (if it implements ). Creates a new instance of the object. The command that is bring wrapped The method under test Executes the test method. Creates a new instance of the class under tests and passes it to the inner command. Also catches any exceptions and converts them into s. The instance of the test class Returns information about the test run Command used to wrap a which has associated fixture data. Creates a new instance of the class. The inner command The fixtures to be set on the test class Sets the fixtures on the test class by calling SetFixture, then calls the inner command. The instance of the test class Returns information about the test run A timer class used to figure out how long tests take to run. On most .NET implementations this will use the class because it's a high resolution timer; however, on Silverlight/CoreCLR, it will use (which will provide lower resolution results). Creates a new instance of the class. Starts timing. Stops timing. Gets how long the timer ran, in milliseconds. In order for this to be valid, both and must have been called. Attribute used to decorate a test method with arbitrary name/value pairs ("traits"). Creates a new instance of the class. The trait name The trait value Gets the trait name. Gets the trait value. Runner that executes an synchronously. Execute the . The test class command to execute The methods to execute; if null or empty, all methods will be executed The start run callback The end run result callback A with the results of the test run Factory for objects, based on the type under test. Creates the test class command, which implements , for a given type. The type under test The test class command, if the class is a test class; null, otherwise Creates the test class command, which implements , for a given type. The type under test The test class command, if the class is a test class; null, otherwise Represents an xUnit.net test class Interface which describes the ability to executes all the tests in a test class. Allows the test class command to choose the next test to be run from the list of tests that have not yet been run, thereby allowing it to choose the run order. The tests remaining to be run The index of the test that should be run Execute actions to be run after all the test methods of this test class are run. Returns the thrown during execution, if any; null, otherwise Execute actions to be run before any of the test methods of this test class are run. Returns the thrown during execution, if any; null, otherwise Enumerates the test commands for a given test method in this test class. The method under test The test commands for the given test method Enumerates the methods which are test methods in this test class. The test methods Determines if a given refers to a test method. The test method to validate True if the method is a test method; false, otherwise Gets the object instance that is under test. May return null if you wish the test framework to create a new object instance for each test method. Gets or sets the type that is being tested Creates a new instance of the class. Creates a new instance of the class. The type under test Creates a new instance of the class. The type under test Chooses the next test to run, randomly, using the . The tests remaining to be run The index of the test that should be run Execute actions to be run after all the test methods of this test class are run. Returns the thrown during execution, if any; null, otherwise Execute actions to be run before any of the test methods of this test class are run. Returns the thrown during execution, if any; null, otherwise Enumerates the test commands for a given test method in this test class. The method under test The test commands for the given test method Enumerates the methods which are test methods in this test class. The test methods Determines if a given refers to a test method. The test method to validate True if the method is a test method; false, otherwise Gets the object instance that is under test. May return null if you wish the test framework to create a new object instance for each test method. Gets or sets the randomizer used to determine the order in which tests are run. Sets the type that is being tested Implementation of that represents a skipped test. Creates a new instance of the class. The method that is being skipped The display name for the test. If null, the fully qualified type name is used. The reason the test was skipped. Gets the skip reason. Factory for creating objects. Make instances of objects for the given class and method. The class command The method under test The set of objects A command wrapper which times the running of a command. Creates a new instance of the class. The command that will be timed. Executes the inner test method, gathering the amount of time it takes to run. Returns information about the test run Wraps a command which should fail if it runs longer than the given timeout value. Creates a new instance of the class. The command to be run The timout, in milliseconds The method under test Executes the test method, failing if it takes too long. Returns information about the test run Attributes used to decorate a test fixture that is run with an alternate test runner. The test runner must implement the interface. Creates a new instance of the class. The class which implements ITestClassCommand and acts as the runner for the test fixture. Gets the test class command. Exception thrown when two object references are unexpectedly not the same instance. Creates a new instance of the class. The expected object reference The actual object reference Contains the test results from an assembly. Contains multiple test results, representing them as a composite test result. Adds a test result to the composite test result list. Gets the test results. Creates a new instance of the class. The filename of the assembly Creates a new instance of the class. The filename of the assembly The configuration filename Converts the test result into XML that is consumed by the test runners. The parent node. The newly created XML node. Gets the fully qualified filename of the configuration file. Gets the directory where the assembly resides. Gets the number of failed results. Gets the fully qualified filename of the assembly. Gets the number of passed results. Gets the number of skipped results. Contains the test results from a test class. Creates a new instance of the class. The type under test Creates a new instance of the class. The simple name of the type under test The fully qualified name of the type under test The namespace of the type under test Sets the exception thrown by the test fixture. The thrown exception Converts the test result into XML that is consumed by the test runners. The parent node. The newly created XML node. Gets the fully qualified test fixture exception type, when an exception has occurred. Gets the number of tests which failed. Gets the fully qualified name of the type under test. Gets the test fixture exception message, when an exception has occurred. Gets the simple name of the type under test. Gets the namespace of the type under test. Gets the number of tests which passed. Gets the number of tests which were skipped. Gets the test fixture exception stack trace, when an exception has occurred. Represents a failed test result. Represents the results from running a test method Initializes a new instance of the class. The traits for the test method are discovered using reflection. The method under test. The display name for the test. If null, the fully qualified type name is used. Initializes a new instance of the class. The name of the method under test. The type of the method under test. The display name for the test. If null, the fully qualified type name is used. The traits. Converts the test result into XML that is consumed by the test runners. The parent node. The newly created XML node. Gets or sets the display name of the method under test. This is the value that's shown during failures and in the resulting output XML. Gets the name of the method under test. Gets or sets the standard output/standard error from the test that was captured while the test was running. Gets the traits attached to the test method. Gets the name of the type under test. Creates a new instance of the class. The method under test The exception throw by the test The display name for the test. If null, the fully qualified type name is used. Creates a new instance of the class. The name of the method under test The name of the type under test The display name of the test The custom properties attached to the test method The full type name of the exception throw The exception message The exception stack trace Converts the test result into XML that is consumed by the test runners. The parent node. The newly created XML node. Gets the exception type thrown by the test method. Gets the exception message thrown by the test method. Gets the stack trace of the exception thrown by the test method. Represents a passing test result. Create a new instance of the class. The method under test The display name for the test. If null, the fully qualified type name is used. Create a new instance of the class. The name of the method under test The name of the type under test The display name for the test. If null, the fully qualified type name is used. The custom properties attached to the test method Converts the test result into XML that is consumed by the test runners. The parent node. The newly created XML node. Represents a skipped test result. Creates a new instance of the class. Uses reflection to discover the skip reason. The method under test The display name for the test. If null, the fully qualified type name is used. The reason the test was skipped. Creates a new instance of the class. The name of the method under test The name of the type under test The display name for the test. If null, the fully qualified type name is used. The traits attached to the method under test The skip reason Converts the test result into XML that is consumed by the test runners. The parent node. The newly created XML node. Gets the skip reason. Represents information about an attribute. Gets the instance of the attribute, if available. The type of the attribute The instance of the attribute, if available. Gets an initialized property value of the attribute. The type of the property The name of the property The property value Represents information about a method. Creates an instance of the type where this test method was found. If using reflection, this should be the ReflectedType. A new instance of the type. Gets all the custom attributes for the method that are of the given type. The type of the attribute The matching attributes that decorate the method Determines if the method has at least one instance of the given attribute type. The type of the attribute True if the method has at least one instance of the given attribute type; false, otherwise Invokes the test on the given class, with the given parameters. The instance of the test class (may be null if the test method is static). The parameters to be passed to the test method. Gets a value which represents the class that this method was reflected from (i.e., equivalent to MethodInfo.ReflectedType) Gets a value indicating whether the method is abstract. Gets a value indicating whether the method is static. Gets the underlying for the method, if available. Gets the name of the method. Gets the fully qualified type name of the return type. Gets the fully qualified type name of the type that this method belongs to. If using reflection, this should be the ReflectedType. Represents information about a type. Gets all the custom attributes for the type that are of the given attribute type. The type of the attribute The matching attributes that decorate the type Gets a test method by name. The name of the method The method, if it exists; null, otherwise. Gets all the methods Determines if the type has at least one instance of the given attribute type. The type of the attribute True if the type has at least one instance of the given attribute type; false, otherwise Determines if the type implements the given interface. The type of the interface True if the type implements the given interface; false, otherwise Gets a value indicating whether the type is abstract. Gets a value indicating whether the type is sealed. Gets the underlying object, if available. Utility class which inspects methods for test information Gets the display name. The method to be inspected The display name Gets the skip reason from a test method. The method to be inspected The skip reason Gets the test commands for a test method. The method to be inspected The objects for the test method Gets the timeout value for a test method. The method to be inspected The timeout, in milliseconds Gets the traits on a test method. The method to be inspected A dictionary of the traits Determines whether a test method has a timeout. The method to be inspected True if the method has a timeout; false, otherwise Determines whether a test method has traits. The method to be inspected True if the method has traits; false, otherwise Determines whether a test method should be skipped. The method to be inspected True if the method should be skipped; false, otherwise Determines whether a method is a test method. A test method must be decorated with the (or derived class) and must not be abstract. The method to be inspected True if the method is a test method; false, otherwise Wrapper to implement and using reflection. Converts an into an using reflection. Converts a into an using reflection. The method to wrap The wrapper Converts a into an using reflection. The type to wrap The wrapper Utility class which inspects types for test information Determines if a type contains any test methods The type to be inspected True if the class contains any test methods; false, otherwise Retrieves the type to run the test class with from the , if present. The type to be inspected The type of the test class runner, if present; null, otherwise Retrieves a list of the test methods from the test class. The type to be inspected The test methods Determines if the test class has a applied to it. The type to be inspected True if the test class has a run with attribute; false, otherwise Determines if the type implements . The type to be inspected True if the type implements ; false, otherwise Determines whether the specified type is abstract. The type. true if the specified type is abstract; otherwise, false. Determines whether the specified type is static. The type. true if the specified type is static; otherwise, false. Determines if a class is a test class. The type to be inspected True if the type is a test class; false, otherwise Attribute that is applied to a method to indicate that it is a fact that should be run by the test runner. It can also be extended to support a customized definition of a test method. Creates instances of which represent individual intended invocations of the test method. The method under test An enumerator through the desired test method invocations Enumerates the test commands represented by this test method. Derived classes should override this method to return instances of , one per execution of a test method. The test method The test commands which will execute the test runs for the given method Gets the name of the test to be used when the test is skipped. Defaults to null, which will cause the fully qualified test name to be used. Obsolete. Please use the property instead. Marks the test so that it will not be run, and gets or sets the skip reason Marks the test as failing if it does not finish running within the given time period, in milliseconds; set to 0 or less to indicate the method has no timeout Exception thrown when code unexpectedly fails to throw an exception. Creates a new instance of the class. Call this constructor when no exception was thrown. The type of the exception that was expected Creates a new instance of the class. Call this constructor when an exception of the wrong type was thrown. The type of the exception that was expected The actual exception that was thrown Gets a string representation of the frames on the call stack at the time the current exception was thrown. A string that describes the contents of the call stack, with the most recent method call appearing first. Exception thrown when a test method exceeds the given timeout value Creates a new instance of the class. The timeout value, in milliseconds Exception thrown when a value is unexpectedly false. Creates a new instance of the class. The user message to be displayed, or null for the default message ================================================ FILE: Build-Solution.ps1 ================================================ $scriptPath = Split-Path $MyInvocation.MyCommand.Path $projFile = join-path $scriptPath WebBackgrounder.msbuild & "$(get-content env:windir)\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" $projFile /t:FullBuild ================================================ FILE: License.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: README.md ================================================ # Intro WebBackgrounder is a proof-of-concept of a web-farm friendly background task manager meant to just work with a vanilla ASP.NET web application. # Open Source Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. # Problem Statement Code that runs within ASP.NET does not own the App Domain. IIS and ASP.NET are free to shut down the app domain at any moment. Typically, when that happens, ASP.NET flushes the requests giving them time to finish what they're doing before tearing down the App Domain. But if you're doing work on background thread that ASP.NET doesn't know about, it could end up tearing down the app domain in the middle of the work, leaving your data in a potentially invalid state. There are ways to notify ASP.NET that work is in progress. WebBackgrounder demonstrates the use of such facilities. Likewise, if you have multiple web servers in a farm, you'd like some small amount of coordination between them without them having to explicitly know about each other so they don't duplicate each other's work. # Why not use Azure Queues or a Scheduled Task? For an enterprise solution, using an Azure Queue, a Windows Service, or a system level scheduled task are much better solutions. The goal of this project would be to allow those to easily hook in and provide this functionality. But for a small project, or for development environments, you still want these tasks to run without requiring a bunch of setup or a connection to Azure. # What this is not This is not a general purpose scheduling framework. There are much better ones out there such as hangfire.io, FluentScheduler and Quartz.net. The goal of this project is to handle one task only, manage a recurring task on an interval in the background for a web app. The needs I have are very simple. I didn't need a high fidelity scheduler. Maybe later, I'll look to integrate what I've done with one of the others. But for now, this scratches an itch. ================================================ FILE: WebBackgrounder.msbuild ================================================ Debug ================================================ FILE: src/.nuget/NuGet.settings.targets ================================================ $(MSBuildProjectDirectory)\..\ $(SolutionDir).nuget $(NuGetToolsPath)\nuget.exe $(ProjectDir)packages.config $(SolutionDir)packages $(TargetDir.Trim('\\')) "" false false "$(NuGetExePath)" install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)" "$(NuGetExePath)" pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols RestorePackages; $(BuildDependsOn); $(BuildDependsOn); BuildPackage; ================================================ FILE: src/.nuget/NuGet.targets ================================================ ================================================ FILE: src/WebBackgrounder/IJob.cs ================================================ using System; using System.Threading.Tasks; namespace WebBackgrounder { public interface IJob { /// /// Identifies the type of job. For example, "UpdateStats" /// string Name { get; } Task Execute(); TimeSpan Interval { get; } TimeSpan Timeout { get; } } } ================================================ FILE: src/WebBackgrounder/IJobCoordinator.cs ================================================ using System; using System.Threading.Tasks; namespace WebBackgrounder { public interface IJobCoordinator : IDisposable { /// /// Coordinates the work to be done and returns a task that wraps /// the work as well as the coordination of that work. /// /// Task GetWork(IJob job); } } ================================================ FILE: src/WebBackgrounder/IJobHost.cs ================================================ using System; using System.Threading.Tasks; namespace WebBackgrounder { /// /// Represents the environment that is hosting the task manager. /// Typically a web application such as ASP.NET. /// public interface IJobHost { void DoWork(Task work); } } ================================================ FILE: src/WebBackgrounder/IWorkItem.cs ================================================ using System; namespace WebBackgrounder { public interface IWorkItem { long Id { get; set; } DateTime Started { get; set; } DateTime? Completed { get; set; } } } ================================================ FILE: src/WebBackgrounder/IWorkItemRepository.cs ================================================ using System; namespace WebBackgrounder { public interface IWorkItemRepository : IDisposable { void RunInTransaction(Action query); IWorkItem GetLastWorkItem(IJob job); long CreateWorkItem(string workerId, IJob job); void SetWorkItemCompleted(long workItemId); void SetWorkItemFailed(long workItemId, Exception exception); } } ================================================ FILE: src/WebBackgrounder/Job.cs ================================================ using System; using System.Threading.Tasks; namespace WebBackgrounder { public abstract class Job : IJob { protected Job(string name, TimeSpan interval, TimeSpan timeout) { Name = name; Interval = interval; Timeout = timeout; } protected Job(string name, TimeSpan interval) : this(name, interval, TimeSpan.MaxValue) { } public string Name { get; private set; } public abstract Task Execute(); public TimeSpan Interval { get; private set; } public TimeSpan Timeout { get; private set; } } } ================================================ FILE: src/WebBackgrounder/JobHost.cs ================================================ using System; using System.Threading.Tasks; using System.Web.Hosting; namespace WebBackgrounder { public class JobHost : IJobHost, IRegisteredObject { readonly object _lock = new object(); bool _shuttingDown; public JobHost() { HostingEnvironment.RegisterObject(this); } public void Stop(bool immediate) { lock (_lock) { _shuttingDown = true; } HostingEnvironment.UnregisterObject(this); } public void DoWork(Task work) { if (work == null) { throw new ArgumentNullException("work"); } lock (_lock) { if (_shuttingDown) { return; } if (work.Status == TaskStatus.Created) { work.Start(); } // Need to hold the lock until the task completes. // Later on, we should take advantage of the fact that the work is represented // by a task. Instead of locking, we could simply have the Stop method cancel // any pending tasks. work.Wait(); } } } } ================================================ FILE: src/WebBackgrounder/JobManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace WebBackgrounder { public class JobManager : IDisposable { readonly IJobHost _host; readonly Timer _timer; readonly IJobCoordinator _coordinator; readonly Scheduler _scheduler; readonly IEnumerable _jobs; Action _failHandler; public bool RestartSchedulerOnFailure { get; set; } public JobManager(IEnumerable jobs, IJobHost host) : this(jobs, host, new SingleServerJobCoordinator()) { } public JobManager(IEnumerable jobs, IJobCoordinator coordinator) : this(jobs, new JobHost(), coordinator) { } public JobManager(IEnumerable jobs, IJobHost host, IJobCoordinator coordinator) { if (jobs == null) { throw new ArgumentNullException("jobs"); } if (host == null) { throw new ArgumentNullException("host"); } if (coordinator == null) { throw new ArgumentNullException("coordinator"); } _jobs = jobs; _scheduler = new Scheduler(jobs); _host = host; _coordinator = coordinator; _timer = new Timer(OnTimerElapsed); } public void Start() { _timer.Next(TimeSpan.FromMilliseconds(1)); } public void Stop() { _timer.Stop(); } void OnTimerElapsed(object sender) { try { _timer.Stop(); DoNextJob(); _timer.Next(_scheduler.Next().GetIntervalToNextRun()); // Start up again. } catch (Exception e) { OnException(e); // Someone else's problem. if (RestartSchedulerOnFailure) { _timer.Next(_scheduler.Next().GetIntervalToNextRun()); // Start up again. } } } void DoNextJob() { using (var schedule = _scheduler.Next()) { var work = _coordinator.GetWork(schedule.Job); if (work != null) { _host.DoWork(work); } } } public void Dispose() { Stop(); foreach (var job in _jobs.OfType()) { job.Dispose(); } _timer.Dispose(); _coordinator.Dispose(); } public void Fail(Action failHandler) { _failHandler = failHandler; } void OnException(Exception e) { var fail = _failHandler; if (fail != null) { fail(e); } } } } ================================================ FILE: src/WebBackgrounder/JobUnitOfWork.cs ================================================ using System; namespace WebBackgrounder { public class JobUnitOfWork { readonly IWorkItemRepository _repository; readonly long _workItemId; public JobUnitOfWork(IWorkItemRepository repository, long workItemId) { _workItemId = workItemId; _repository = repository; } public void Complete() { _repository.SetWorkItemCompleted(_workItemId); } public void Fail(Exception exception) { _repository.SetWorkItemFailed(_workItemId, exception); } } } ================================================ FILE: src/WebBackgrounder/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("WebBackgrounder")] [assembly: AssemblyDescription("WebBackgrounder is a proof-of-concept of a web-farm friendly background task manager meant to just work with a vanilla ASP.NET web application. See https://github.com/NuGet/WebBackgrounder for more information.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany(".NET Foundation")] [assembly: AssemblyProduct("WebBackgrounder")] [assembly: AssemblyCopyright("Copyright © .NET Foundation 2015")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("dcfda96a-65fa-4378-b3d6-d98091614fd2")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.2.0.0")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0")] ================================================ FILE: src/WebBackgrounder/Schedule.cs ================================================ using System; namespace WebBackgrounder { public class Schedule : IDisposable { readonly Func _nowThunk; public Schedule(IJob job) : this(job, () => DateTime.UtcNow) { } public Schedule(IJob job, Func nowThunk) { if (job == null) { throw new ArgumentNullException("job"); } Job = job; _nowThunk = nowThunk; } public IJob Job { get; private set; } public DateTime LastRunTime { get; set; } public DateTime NextRunTime { get { return LastRunTime.Add(Job.Interval); } } public TimeSpan GetIntervalToNextRun() { var now = _nowThunk(); if (NextRunTime < now) { return TimeSpan.FromMilliseconds(1); } return NextRunTime - now; } public void SetRunComplete() { LastRunTime = _nowThunk(); } void IDisposable.Dispose() { SetRunComplete(); } } } ================================================ FILE: src/WebBackgrounder/Scheduler.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace WebBackgrounder { public class Scheduler { readonly IEnumerable _schedules; public Scheduler(IEnumerable jobs, Func nowThunk) { if (jobs.Any(j => j.Interval < TimeSpan.Zero)) { throw new ArgumentException("A job cannot have a negative interval.", "jobs"); } var lastRunTime = nowThunk(); _schedules = jobs.Select(job => new Schedule(job, nowThunk) { LastRunTime = lastRunTime }).ToList(); } public Scheduler(IEnumerable jobs) : this(jobs, () => DateTime.UtcNow) { } public Schedule Next() { var schedules = _schedules.OrderBy(s => s.NextRunTime); return schedules.First(); } } } ================================================ FILE: src/WebBackgrounder/SingleServerJobCoordinator.cs ================================================ using System.Threading.Tasks; namespace WebBackgrounder { public class SingleServerJobCoordinator : IJobCoordinator { public Task GetWork(IJob job) { return job.Execute(); } public void Dispose() { } } } ================================================ FILE: src/WebBackgrounder/TimerExtensions.cs ================================================ using System; using System.Threading; namespace WebBackgrounder { public static class TimerExtensions { public static void Stop(this Timer timer) { timer.Change(Timeout.Infinite, Timeout.Infinite); } public static void Next(this Timer timer, TimeSpan dueTime) { timer.Change(dueTime, TimeSpan.FromMilliseconds(Timeout.Infinite)); } } } ================================================ FILE: src/WebBackgrounder/WebBackgrounder.csproj ================================================  Debug AnyCPU 8.0.30703 2.0 {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E} Library Properties WebBackgrounder WebBackgrounder v4.0 512 ..\ true true true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 Designer ================================================ FILE: src/WebBackgrounder/WebBackgrounder.nuspec ================================================ $id$ $version$ $title$ $author$ $author$ http://nuget.codeplex.com/license https://github.com/NuGet/WebBackgrounder false $description$ * 0.0.1.1 Refactored the IWorkItemRepository interface to be stateless. * 0.0.1.4 Added more robust handling of badly behaving jobs (throwing exceptions). * 0.0.1.6 Fixed a bug in how the next task is scheduled aspnet threading background tasks jobs scheduled scheduling workers asp.net ================================================ FILE: src/WebBackgrounder/WebFarmJobCoordinator.cs ================================================ using System; using System.Threading.Tasks; namespace WebBackgrounder { /// /// Uses the database accessed via EF Code First to coordinate jobs in a web farm. /// public class WebFarmJobCoordinator : IJobCoordinator { readonly static string WebServerWorkerId = Guid.NewGuid().ToString(); readonly IWorkItemRepository _workItemRepository; public WebFarmJobCoordinator(IWorkItemRepository workItemRepository) { if (workItemRepository == null) { throw new ArgumentNullException("workItemRepository"); } _workItemRepository = workItemRepository; } // Returns a task with the work to do if work is available to do and another web server // in the web farm isn't already doing it. public Task GetWork(IJob job) { if (job == null) { throw new ArgumentNullException("job"); } var unitOfWork = ReserveWork(WebServerWorkerId, job); if (unitOfWork == null) { return null; } Task task = null; try { task = job.Execute(); } catch (Exception e) { task = new Task(() => { throw e; }); } task.ContinueWith(c => { if (c.IsFaulted) { unitOfWork.Fail(c.Exception.GetBaseException()); } else { unitOfWork.Complete(); } }); return task; } // if work is available to do and another web server in the web farm isn't already doing, // this returns a unit of work used to wrap the work. public JobUnitOfWork ReserveWork(string workerId, IJob job) { long? workItemId = null; // We do a double check here because this may be the first DB query that runs when starting // WebBackgrounder. For those using EF Code First, this could trigger a database creation // (typically on a dev box, hopefully not in production). A database can't be created inside // a transaction scope so we run this query here first. var lastWorkItem = _workItemRepository.GetLastWorkItem(job); if (lastWorkItem.IsActive() && !lastWorkItem.IsTimedOut(job)) { return null; } _workItemRepository.RunInTransaction(() => { lastWorkItem = _workItemRepository.GetLastWorkItem(job); if (lastWorkItem.IsTimedOut(job)) { lastWorkItem.Completed = DateTime.UtcNow; _workItemRepository.SetWorkItemFailed(lastWorkItem.Id, new TimeoutException("Workitem expired. Job timeout was " + job.Timeout)); lastWorkItem = null; } if (lastWorkItem.IsActive()) { workItemId = null; return; } workItemId = _workItemRepository.CreateWorkItem(workerId, job); } ); if (workItemId == null) { return null; } return new JobUnitOfWork(_workItemRepository, workItemId.Value); } public void Dispose() { var repository = _workItemRepository; if (repository != null) { repository.Dispose(); } } } } ================================================ FILE: src/WebBackgrounder/WorkItemExtensions.cs ================================================ using System; namespace WebBackgrounder { public static class WorkItemExtensions { public static bool IsActive(this IWorkItem workItem) { return workItem != null && workItem.Completed == null; } public static bool IsTimedOut(this IWorkItem workItem, IJob job) { if (job == null) { throw new ArgumentNullException("job"); } return workItem != null && job.Timeout != TimeSpan.MaxValue && workItem.Started.Add(job.Timeout) < DateTime.UtcNow; } } } ================================================ FILE: src/WebBackgrounder.DemoWeb/App_Start/EntityFramework.SqlServerCompact.cs ================================================ using System.Data.Entity; using System.Data.Entity.Infrastructure; [assembly: WebActivator.PreApplicationStartMethod(typeof(WebBackgrounder.DemoWeb.App_Start.EntityFramework_SqlServerCompact), "Start")] namespace WebBackgrounder.DemoWeb.App_Start { public static class EntityFramework_SqlServerCompact { public static void Start() { Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0"); } } } ================================================ FILE: src/WebBackgrounder.DemoWeb/App_Start/WebBackgrounderSetup.cs ================================================ using System; using Elmah; using WebBackgrounder.Jobs; [assembly: WebActivator.PostApplicationStartMethod(typeof(WebBackgrounder.DemoWeb.App_Start.WebBackgrounderSetup), "Start")] [assembly: WebActivator.ApplicationShutdownMethod(typeof(WebBackgrounder.DemoWeb.App_Start.WebBackgrounderSetup), "Shutdown")] namespace WebBackgrounder.DemoWeb.App_Start { public static class WebBackgrounderSetup { static readonly JobManager _jobManager = CreateJobWorkersManager(); public static void Start() { _jobManager.Start(); } public static void Shutdown() { _jobManager.Dispose(); } private static JobManager CreateJobWorkersManager() { var jobs = new IJob[] { new SampleJob(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)), /* new ExceptionJob(TimeSpan.FromSeconds(15)), */ new WorkItemCleanupJob(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(5), new WorkItemsContext()) }; var coordinator = new WebFarmJobCoordinator(new EntityWorkItemRepository(() => new WorkItemsContext())); var manager = new JobManager(jobs, coordinator); manager.Fail(ex => Elmah.ErrorLog.GetDefault(null).Log(new Error(ex))); return manager; } } } ================================================ FILE: src/WebBackgrounder.DemoWeb/Content/Site.css ================================================ body { font-size: .85em; font-family: "Trebuchet MS", Verdana, Helvetica, Sans-Serif; color: #232323; background-color: #fff; } header, footer, nav, section { display: block; } /* Styles for basic forms -----------------------------------------------------------*/ fieldset { border:1px solid #ddd; padding:0 1.4em 1.4em 1.4em; margin:0 0 1.5em 0; } legend { font-size:1.2em; font-weight: bold; } textarea { min-height: 75px; } .editor-label { margin: 1em 0 0 0; } .editor-field { margin:0.5em 0 0 0; } /* Styles for validation helpers -----------------------------------------------------------*/ .field-validation-error { color: #ff0000; } .field-validation-valid { display: none; } .input-validation-error { border: 1px solid #ff0000; background-color: #ffeeee; } .validation-summary-errors { font-weight: bold; color: #ff0000; } .validation-summary-valid { display: none; } td {padding-right: 10px;} ================================================ FILE: src/WebBackgrounder.DemoWeb/Controllers/HomeController.cs ================================================ using System.Linq; using System.Web.Mvc; namespace WebBackgrounder.DemoWeb.Controllers { public class HomeController : Controller { private IWorkItemsContext db = new WorkItemsContext(); public ActionResult Index() { var workers = (from w in db.WorkItems orderby w.Started descending select w).Take(30); return View(workers); } } } ================================================ FILE: src/WebBackgrounder.DemoWeb/ExceptionJob.cs ================================================ using System; using System.Threading.Tasks; namespace WebBackgrounder.DemoWeb { public class ExceptionJob : Job { public ExceptionJob(TimeSpan interval) : base("Sample Job", interval) { } public override Task Execute() { return new Task(() => { throw new InvalidOperationException("This is a test"); }); } } } ================================================ FILE: src/WebBackgrounder.DemoWeb/Global.asax ================================================ <%@ Application Codebehind="Global.asax.cs" Inherits="WebBackgrounder.DemoWeb.MvcApplication" Language="C#" %> ================================================ FILE: src/WebBackgrounder.DemoWeb/Global.asax.cs ================================================ using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace WebBackgrounder.DemoWeb { // Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } } } ================================================ FILE: src/WebBackgrounder.DemoWeb/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("WebBackgrounder.DemoWeb")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Microsoft")] [assembly: AssemblyProduct("WebBackgrounder.DemoWeb")] [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("9ca43cf3-f32e-4151-b218-345a3502e24a")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: src/WebBackgrounder.DemoWeb/SampleJob.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace WebBackgrounder.DemoWeb { public class SampleJob : Job { public SampleJob(TimeSpan interval, TimeSpan timeout) : base("Sample Job", interval, timeout) { } public override Task Execute() { return new Task(() => Thread.Sleep(3000)); } } } ================================================ FILE: src/WebBackgrounder.DemoWeb/Views/Home/Index.cshtml ================================================ @model IEnumerable @{ ViewBag.Title = "Index"; }

WebBackgrounder Demo Site

This site is used to test and demonstrate WebBackgrounder

@foreach (var item in Model) { }
Id Name WorkerId Started Completed Elapsed ExceptionInfo
@Html.DisplayFor(modelItem => item.Id) @Html.DisplayFor(modelItem => item.JobName) @item.WorkerId.ToString().Substring(0, 8) @Html.DisplayFor(modelItem => item.Started) @Html.DisplayFor(modelItem => item.Completed) @Elapsed(item) @Html.DisplayFor(modelItem => item.ExceptionInfo)
@helper Elapsed(WorkItem workItem) { if (workItem.Completed == null) { @((DateTime.UtcNow - workItem.Started).TotalSeconds)s so far... } else { @((workItem.Completed.Value - workItem.Started).TotalSeconds)s } } ================================================ FILE: src/WebBackgrounder.DemoWeb/Views/Shared/Error.cshtml ================================================ @{ Layout = null; } Error

Sorry, an error occurred while processing your request.

================================================ FILE: src/WebBackgrounder.DemoWeb/Views/Shared/_Layout.cshtml ================================================  @ViewBag.Title @RenderBody() ================================================ FILE: src/WebBackgrounder.DemoWeb/Views/Web.config ================================================ 
================================================ FILE: src/WebBackgrounder.DemoWeb/Views/_ViewStart.cshtml ================================================ @{ Layout = "~/Views/Shared/_Layout.cshtml"; } ================================================ FILE: src/WebBackgrounder.DemoWeb/Web.Debug.config ================================================  ================================================ FILE: src/WebBackgrounder.DemoWeb/Web.Release.config ================================================  ================================================ FILE: src/WebBackgrounder.DemoWeb/Web.config ================================================ 
================================================ FILE: src/WebBackgrounder.DemoWeb/WebBackgrounder.DemoWeb.csproj ================================================  Debug AnyCPU 2.0 {0A004B9F-4440-4C05-9D84-E38726D6162F} {E53F8FEA-EAE0-44A6-8774-FFD645390401};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} Library Properties WebBackgrounder.DemoWeb WebBackgrounder.DemoWeb v4.0 false false ..\ true false true full false bin\ DEBUG;TRACE prompt 4 pdbonly true bin\ TRACE prompt 4 ..\packages\elmah.corelibrary.1.2\lib\Elmah.dll ..\packages\EntityFramework.4.1.10715.0\lib\EntityFramework.dll True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll True ..\packages\SqlServerCompact.4.0.8482.1\lib\System.Data.SqlServerCe.dll True ..\packages\EntityFramework.SqlServerCompact.4.1.8482.2\lib\System.Data.SqlServerCe.Entity.dll False ..\packages\WebActivator.1.4.4\lib\net40\WebActivator.dll Global.asax Web.config Web.config {06D8DE5D-F101-4CD5-B406-8A211216FCE1} WebBackgrounder.EntityFramework {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E} WebBackgrounder False True 53378 / False False False if not exist "$(TargetDir)x86" md "$(TargetDir)x86" xcopy /s /y "$(SolutionDir)packages\SqlServerCompact.4.0.8482.1\NativeBinaries\x86\*.*" "$(TargetDir)x86" if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64" xcopy /s /y "$(SolutionDir)packages\SqlServerCompact.4.0.8482.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64" ================================================ FILE: src/WebBackgrounder.DemoWeb/packages.config ================================================  ================================================ FILE: src/WebBackgrounder.EntityFramework/Entities/IWorkItemsContext.cs ================================================ using System; using System.Data.Entity; namespace WebBackgrounder { public interface IWorkItemsContext : IDisposable { IDbSet WorkItems { get; set; } int SaveChanges(); } } ================================================ FILE: src/WebBackgrounder.EntityFramework/Entities/WorkItem.cs ================================================ using System; using System.ComponentModel.DataAnnotations; namespace WebBackgrounder { public class WorkItem : IWorkItem { public long Id { get; set; } [StringLength(64)] public string JobName { get; set; } [StringLength(64)] public string WorkerId { get; set; } public DateTime Started { get; set; } public DateTime? Completed { get; set; } public string ExceptionInfo { get; set; } } } ================================================ FILE: src/WebBackgrounder.EntityFramework/Entities/WorkItemsContext.cs ================================================ using System.Data.Entity; namespace WebBackgrounder { public class WorkItemsContext : DbContext, IWorkItemsContext { public IDbSet WorkItems { get; set; } } } ================================================ FILE: src/WebBackgrounder.EntityFramework/EntityWorkItemRepository.cs ================================================ using System; using System.Data.Entity.Infrastructure; using System.Linq; using System.Transactions; namespace WebBackgrounder { public class EntityWorkItemRepository : IWorkItemRepository, IDisposable { Func _contextThunk; IWorkItemsContext _context; public EntityWorkItemRepository(Func contextThunk) { _contextThunk = contextThunk; _context = _contextThunk(); } public void RunInTransaction(Action query) { using (var transaction = new TransactionScope()) { // For some reason, I get different behavior when I use this // instead of _context.Database.Connection. This works, that doesn't. :( ((IObjectContextAdapter)_context).ObjectContext.Connection.Open(); query(); transaction.Complete(); } // REVIEW: Make sure this is really needed. I kept running into // exceptions when I didn't do this, but I may be doing it wrong. -Phil 10/17/2011 _context.Dispose(); _context = _contextThunk(); } public IWorkItem GetLastWorkItem(IJob job) { return (from w in _context.WorkItems where w.JobName == job.Name orderby w.Started descending select w).FirstOrDefault() as IWorkItem; } public long CreateWorkItem(string workerId, IJob job) { var workItem = new WorkItem { JobName = job.Name, WorkerId = workerId, Started = DateTime.UtcNow, Completed = null }; _context.WorkItems.Add(workItem); _context.SaveChanges(); return workItem.Id; } public void SetWorkItemCompleted(long workItemId) { var workItem = GetWorkItem(workItemId); workItem.Completed = DateTime.UtcNow; _context.SaveChanges(); } public void SetWorkItemFailed(long workItemId, Exception exception) { var workItem = GetWorkItem(workItemId); workItem.Completed = DateTime.UtcNow; workItem.ExceptionInfo = exception.Message + Environment.NewLine + exception.StackTrace; _context.SaveChanges(); } private WorkItem GetWorkItem(long workerId) { return _context.WorkItems.Find(workerId); } public void Dispose() { var context = _context; if (context != null) { context.Dispose(); } } } } ================================================ FILE: src/WebBackgrounder.EntityFramework/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("WebBackgrounder.EntityFramework")] [assembly: AssemblyDescription("WebBackgrounder.EntityFramework is an implementation of the IJobCoordinator for WebBackgrounder that uses a Database (via EF Code First) as the synchronization mechanism (aka a mutex) for coordinating jobs.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany(".NET Foundation")] [assembly: AssemblyProduct("WebBackgrounder")] [assembly: AssemblyCopyright("Copyright © .NET Foundation 2015")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("724b4dbb-4758-4270-b4d9-c33f9a1a4e4c")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.1.0.0")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0-beta")] ================================================ FILE: src/WebBackgrounder.EntityFramework/WebBackgrounder.EntityFramework.csproj ================================================  Debug AnyCPU 8.0.30703 2.0 {06D8DE5D-F101-4CD5-B406-8A211216FCE1} Library Properties WebBackgrounder.EntityFramework WebBackgrounder.EntityFramework v4.0 512 ..\ true true true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 ..\packages\EntityFramework.4.1.10715.0\lib\EntityFramework.dll True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll ..\packages\WebActivator.1.4.4\lib\net40\WebActivator.dll Designer {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E} WebBackgrounder ================================================ FILE: src/WebBackgrounder.EntityFramework/WebBackgrounder.EntityFramework.nuspec ================================================ $id$ $version$ $title$ $author$ $author$ http://nuget.codeplex.com/license https://github.com/NuGet/WebBackgrounder false $description$ Updated this to take into account API changes in WebBackgrounder. aspnet threading background tasks jobs scheduled scheduling workers asp.net sql entity-framework database db ================================================ FILE: src/WebBackgrounder.EntityFramework/WorkItemCleanupJob.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; namespace WebBackgrounder.Jobs { public class WorkItemCleanupJob : Job { readonly IWorkItemsContext _context; public WorkItemCleanupJob(TimeSpan interval, TimeSpan spanToKeepRecords, IWorkItemsContext context) : base("WorkItem Table Cleanup", interval) { if (spanToKeepRecords < TimeSpan.Zero) { throw new ArgumentException("Need to specify a positive time span.", "spanToKeepRecords"); } SpanToKeepRecords = spanToKeepRecords; _context = context; } /// /// When this number is reached, this task will delete the oldest /// work items larger than this number. /// public TimeSpan SpanToKeepRecords { get; private set; } public override Task Execute() { return new Task(() => { var cutoffDate = DateTime.UtcNow.Subtract(SpanToKeepRecords); var oldItems = _context.WorkItems.Where(w => w.Completed != null && w.Completed < cutoffDate); if (oldItems.Any()) { foreach (var workItem in oldItems.ToList()) { _context.WorkItems.Remove(workItem); } _context.SaveChanges(); } }); } } } ================================================ FILE: src/WebBackgrounder.EntityFramework/packages.config ================================================  ================================================ FILE: src/WebBackgrounder.UnitTests/EntityWorkItemRepositoryFacts.cs ================================================ using System; using System.Data.Entity; using System.Linq; using Moq; using Xunit; namespace WebBackgrounder.UnitTests { public class EntityWorkItemRepositoryFacts { public class TheGetLastWorkItemMethod { [Fact] public void ReturnsActiveWorkItemWhenAWorkerHasNoCompletedDate() { var job = new Mock(); job.Setup(j => j.Name).Returns("docoolstuffjobname"); var workItems = new InMemoryDbSet { new WorkItem { JobName = job.Object.Name, Started = DateTime.UtcNow, Completed = null } }; var context = new Mock(); context.Object.WorkItems = workItems; var repository = new EntityWorkItemRepository(() => context.Object); var activeWorkItem = repository.GetLastWorkItem(job.Object); Assert.True(activeWorkItem.IsActive()); } [Fact] public void ReturnsLastInactiveWorkItemWhenAllWorkItemsHaveCompletedDate() { var job = new Mock(); job.Setup(j => j.Name).Returns("docoolstuff"); var workItems = new InMemoryDbSet { new WorkItem { Id = 1, JobName = job.Object.Name, Started = DateTime.UtcNow.AddMinutes(-1), Completed = DateTime.UtcNow.AddMinutes(-1) }, new WorkItem { Id = 2, JobName = job.Object.Name, Started = DateTime.UtcNow, Completed = DateTime.UtcNow } }; var context = new Mock(); context.Object.WorkItems = workItems; var repository = new EntityWorkItemRepository(() => context.Object); var activeWorker = repository.GetLastWorkItem(job.Object); Assert.Equal(2, activeWorker.Id); Assert.False(activeWorker.IsActive()); } [Fact] public void ReturnsNullWhenNoWorkItemsReturnedForGivenJob() { var job = new Mock(); job.Setup(j => j.Name).Returns("docoolstuff"); var workItems = new InMemoryDbSet { new WorkItem { JobName = "DoNotDoAnyCoolStuff", Started = DateTime.UtcNow, Completed = DateTime.UtcNow } }; var context = new Mock(); context.Object.WorkItems = workItems; var repository = new EntityWorkItemRepository(() => context.Object); var activeWorkItem = repository.GetLastWorkItem(job.Object); Assert.Null(activeWorkItem); } } public class TheCreateWorkItemMethod { public void CreatesNewWorkItemThatIsNotComplete() { var job = new Mock(); job.Setup(j => j.Name).Returns("do-cool-stuff"); var before = DateTime.UtcNow; var context = new Mock(); context.Setup(c => c.SaveChanges()).Verifiable(); context.Object.WorkItems = new InMemoryDbSet(); var repository = new EntityWorkItemRepository(() => context.Object); repository.CreateWorkItem("web-server-1", job.Object); var created = context.Object.WorkItems.First(w => w.WorkerId == "web-server-1"); Assert.NotNull(created); Assert.Null(created.Completed); Assert.Equal("do-cool-stuff", created.JobName); Assert.True(created.Started >= before); context.Verify(); } } public class TheSetWorkItemCompleteMethod { [Fact] public void SetsWorkItemCompletedSetsCompletedDate() { const long workItemId = 123; var context = new Mock(); var workItem = new WorkItem { Id = workItemId }; var workItems = new Mock>(); workItems.Setup(w => w.Find(workItemId)).Returns(workItem); context.Object.WorkItems = workItems.Object; var repository = new EntityWorkItemRepository(() => context.Object); repository.SetWorkItemCompleted(workItemId); Assert.NotNull(workItem.Completed); } [Fact] public void SavesChanges() { var context = new Mock(); var workItems = new Mock>(); workItems.Setup(w => w.Find(It.IsAny())).Returns(new WorkItem()); context.Object.WorkItems = workItems.Object; context.Setup(c => c.SaveChanges()).Verifiable(); var repository = new EntityWorkItemRepository(() => context.Object); repository.SetWorkItemCompleted(123); context.Verify(); } } public class TheSetWorkItemFailedMethod { [Fact] public void SetsWorkItemFailedSetsCompletedDateAndExceptionInfo() { const long workItemId = 123; var context = new Mock(); var workItem = new WorkItem { Id = workItemId }; var workItems = new Mock>(); workItems.Setup(w => w.Find(workItemId)).Returns(workItem); context.Object.WorkItems = workItems.Object; var repository = new EntityWorkItemRepository(() => context.Object); repository.SetWorkItemFailed(workItemId, new InvalidOperationException("Pretend failure!")); Assert.NotNull(workItem.Completed); Assert.Contains("Pretend failure!", workItem.ExceptionInfo); } [Fact] public void SavesChanges() { var context = new Mock(); var workItems = new Mock>(); workItems.Setup(w => w.Find(It.IsAny())).Returns(new WorkItem()); context.Object.WorkItems = workItems.Object; context.Setup(c => c.SaveChanges()).Verifiable(); var repository = new EntityWorkItemRepository(() => context.Object); repository.SetWorkItemFailed(123, new InvalidOperationException()); context.Verify(); } } } } ================================================ FILE: src/WebBackgrounder.UnitTests/InMemoryDbSet.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; using System.Linq; using System.Linq.Expressions; namespace WebBackgrounder.UnitTests { public class InMemoryDbSet : IDbSet where TEntity : class { readonly HashSet _set; readonly IQueryable _queryableSet; public InMemoryDbSet() : this(Enumerable.Empty()) { } public InMemoryDbSet(IEnumerable entities) { _set = new HashSet(); foreach (var entity in entities) { _set.Add(entity); } _queryableSet = _set.AsQueryable(); } public TEntity Add(TEntity entity) { _set.Add(entity); return entity; } public TEntity Attach(TEntity entity) { _set.Add(entity); return entity; } public TEntity Remove(TEntity entity) { _set.Remove(entity); return entity; } public virtual TDerivedEntity Create() where TDerivedEntity : class, TEntity { throw new NotImplementedException(); } public virtual TEntity Create() { throw new NotImplementedException(); } public virtual TEntity Find(params object[] keyValues) { throw new NotImplementedException(); } public ObservableCollection Local { get { return new ObservableCollection(_queryableSet); } } public IEnumerator GetEnumerator() { return _queryableSet.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _queryableSet.GetEnumerator(); } public Type ElementType { get { return _queryableSet.ElementType; } } public Expression Expression { get { return _queryableSet.Expression; } } public IQueryProvider Provider { get { return _queryableSet.Provider; } } } } ================================================ FILE: src/WebBackgrounder.UnitTests/JobHostFacts.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Xunit; namespace WebBackgrounder.UnitTests { public class JobHostFacts { public class TheStopMethod { [Fact] public void EnsuresNoWorkIsDone() { var host = new JobHost(); var task = new Task(() => { throw new InvalidOperationException("Hey, this is supposed to be shut down!"); }); host.Stop(true); host.DoWork(task); } [Fact] public void WaitsForTaskToComplete() { var host = new JobHost(); var workTask = new Task(() => host.DoWork(new Task(() => { // Was getting inconsistent results with Thread.Sleep(100) for (int i = 0; i < 100; i++) { Thread.Sleep(1); } }))); var beforeStop = DateTime.UtcNow; workTask.Start(); while (workTask.Status != TaskStatus.Running) { Thread.Sleep(1); } host.Stop(false); var afterStop = DateTime.UtcNow; // If Stop didn't wait, we'd expect after to be less than 100 ms larger than beforeStop Assert.True((afterStop - beforeStop).TotalMilliseconds >= 100); } } public class TheDoWorkMethod { [Fact] public void ThrowsArgumentNullExceptionIfWorkIsNull() { var host = new JobHost(); var exception = Assert.Throws(() => host.DoWork(null)); Assert.Equal("work", exception.ParamName); } [Fact] public void DoesNotCallStartIfWorkIsAlreadyScheduledOrCompleted() { var tcs = new TaskCompletionSource(); tcs.SetResult(null); var task = tcs.Task; var host = new JobHost(); Assert.DoesNotThrow(() => host.DoWork(task)); } } } } ================================================ FILE: src/WebBackgrounder.UnitTests/JobManagerFacts.cs ================================================ using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using Moq; using Xunit; namespace WebBackgrounder.UnitTests { public class JobManagerFacts { public class TheConstructor { [Fact] public void ThrowsExceptionWhenAnyParameterIsNull() { Assert.Throws(() => new JobManager(null, new Mock().Object)); Assert.Throws(() => new JobManager(new[] { new Mock().Object }, (IJobHost)null)); Assert.Throws(() => new JobManager(new[] { new Mock().Object }, (IJobCoordinator)null)); } } public class TheStartMethod { [Fact] public void GetsTheJobDone() { var jobDoneEvent = new ManualResetEvent(false); var job = new Mock(); job.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(1)); var jobs = new[] { job.Object }; var coordinator = new Mock(); coordinator.Setup(c => c.GetWork(job.Object)).Returns(new Task(() => jobDoneEvent.Set())); using (var manager = new JobManager(jobs, coordinator.Object)) { manager.Start(); Assert.True(jobDoneEvent.WaitOne(TimeSpan.FromSeconds(2))); } } [Fact] public void GetsTheJobsDoneInTheRightOrder() { var completed = new ConcurrentQueue(); var longerJobDoneEvent = new ManualResetEvent(false); var shorterJobDoneEvent = new ManualResetEvent(false); var longerJob = new Mock(); var shorterJob = new Mock(); longerJob.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(4)); shorterJob.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(3)); var jobs = new[] { longerJob.Object, shorterJob.Object }; var coordinator = new Mock(); coordinator.Setup(c => c.GetWork(shorterJob.Object)).Returns((Task)null).Callback(() => { completed.Enqueue("shortJob"); shorterJobDoneEvent.Set(); }); coordinator.Setup(c => c.GetWork(longerJob.Object)).Returns((Task)null).Callback(() => { completed.Enqueue("longJob"); longerJobDoneEvent.Set(); }); using (var manager = new JobManager(jobs, coordinator.Object)) { manager.Start(); Assert.True(longerJobDoneEvent.WaitOne(TimeSpan.FromSeconds(5))); Assert.True(shorterJobDoneEvent.WaitOne(TimeSpan.Zero)); } var ordered = completed.ToArray(); Assert.Equal("shortJob", ordered[0]); Assert.Equal("longJob", ordered[1]); } [Fact] public void DoesNotThrowExceptionWhenThereIsNoWorkToDo() { var jobNoTask = new Mock(); jobNoTask.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(1)); var secondJob = new Mock(); secondJob.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(2)); var jobs = new[] { jobNoTask.Object, secondJob.Object }; var firstJobCompleteEvent = new ManualResetEvent(false); var coordinator = new Mock(); coordinator.Setup(c => c.GetWork(jobNoTask.Object)).Returns((Task)null); coordinator.Setup(c => c.GetWork(secondJob.Object)).Returns((Task)null).Callback(() => firstJobCompleteEvent.Set()); bool failed = false; using (var manager = new JobManager(jobs, coordinator.Object)) { manager.Fail(e => failed = true); manager.Start(); Assert.True(firstJobCompleteEvent.WaitOne(TimeSpan.FromSeconds(1))); } Assert.False(failed); } [Fact] public void SchedulerRestartsWhenJobFailsAndRestartSchedulerIsTrue() { var jobNoTask = new Mock(); jobNoTask.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(1)); var secondJob = new Mock(); secondJob.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(2)); var jobs = new[] { jobNoTask.Object, secondJob.Object }; var firstJobCompleteEvent = new ManualResetEvent(false); var coordinator = new Mock(); coordinator.Setup(c => c.GetWork(jobNoTask.Object)).Throws(); coordinator.Setup(c => c.GetWork(secondJob.Object)).Returns((Task)null).Callback(() => firstJobCompleteEvent.Set()); using (var manager = new JobManager(jobs, coordinator.Object)) { manager.RestartSchedulerOnFailure = true; manager.Start(); Assert.True(firstJobCompleteEvent.WaitOne(TimeSpan.FromSeconds(1))); } } [Fact] public void SchedulerDoesNotRestartWhenJobFailsAndRestartSchedulerIsFalse() { var jobNoTask = new Mock(); jobNoTask.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(1)); var secondJob = new Mock(); secondJob.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(2)); var jobs = new[] { jobNoTask.Object, secondJob.Object }; var firstJobCompleteEvent = new ManualResetEvent(false); var coordinator = new Mock(); coordinator.Setup(c => c.GetWork(jobNoTask.Object)).Throws(); coordinator.Setup(c => c.GetWork(secondJob.Object)).Returns((Task)null).Callback(() => firstJobCompleteEvent.Set()); using (var manager = new JobManager(jobs, coordinator.Object)) { manager.Start(); Assert.False(firstJobCompleteEvent.WaitOne(TimeSpan.FromSeconds(1))); } } } } } ================================================ FILE: src/WebBackgrounder.UnitTests/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("WebBackgrounder.UnitTests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Microsoft")] [assembly: AssemblyProduct("WebBackgrounder.UnitTests")] [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("9ada0d62-d85e-4489-bb70-3f91a11b20ef")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: src/WebBackgrounder.UnitTests/ScheduleFacts.cs ================================================ using System; using System.Collections.Generic; using Moq; using Xunit; namespace WebBackgrounder.UnitTests { public class ScheduleFacts { public class TheNextRunTimeProperty { [Fact] public void ReturnsIntervalAddedToLastRunTime() { var job = new Mock(); job.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(32)); var lastRunTime = DateTime.UtcNow.AddSeconds(-10); var schedule = new Schedule(job.Object) {LastRunTime = lastRunTime}; var next = schedule.NextRunTime; Assert.Equal(lastRunTime.AddSeconds(32), next); } } public class TheGetIntervalToNextRunMethod { [Fact] public void ReturnsTheSpanBetweenNowAndNextRunTime() { var now = DateTime.UtcNow; var job = new Mock(); job.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(30)); var schedule = new Schedule(job.Object, () => now) {LastRunTime = now}; var interval = schedule.GetIntervalToNextRun(); Assert.Equal(30, interval.TotalSeconds); } [Fact] public void ReturnsTheSpanBetweenNowAndNextRunTimeFiguringInLastRun() { var now = DateTime.UtcNow; var job = new Mock(); job.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(30)); var schedule = new Schedule(job.Object, () => now) { LastRunTime = now.AddSeconds(-20)}; var interval = schedule.GetIntervalToNextRun(); Assert.Equal(10, interval.TotalSeconds); } [Fact] public void ReturnsOneMillisecondTimeSpanWhenNextRunIsInThePast() { var job = new Mock(); job.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(30)); var schedule = new Schedule(job.Object) { LastRunTime = DateTime.UtcNow.AddSeconds(-40) }; var interval = schedule.GetIntervalToNextRun(); Assert.Equal(1, interval.TotalMilliseconds); } [Fact] public void ShrinksOverTimeEvenWhenLastRunIsNull() { var startDate = DateTime.UtcNow; var dates = new Queue(new[]{ startDate, startDate.AddMilliseconds(10) }); var job = new Mock(); job.Setup(j => j.Interval).Returns(TimeSpan.FromMilliseconds(30)); var schedule = new Schedule(job.Object, dates.Dequeue) { LastRunTime = startDate}; var firstInterval = schedule.GetIntervalToNextRun(); var secondInterval = schedule.GetIntervalToNextRun(); Assert.Equal(30, firstInterval.TotalMilliseconds); Assert.Equal(20, secondInterval.TotalMilliseconds); } } public class TheConstructor { [Fact] public void SetsTheJob() { var job = new Mock(); job.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(30)); var schedule = new Schedule(job.Object); Assert.Equal(job.Object, schedule.Job); } [Fact] public void ThrowsArgumentNullExceptionForNullJob() { Assert.Throws(() => new Schedule(null)); } } public class TheDisposeMethod { [Fact] public void SetsLastRunTimeToNow() { var now = DateTime.UtcNow; var job = new Mock(); job.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(30)); var schedule = new Schedule(job.Object, () => now) {LastRunTime = DateTime.UtcNow.AddSeconds(-100)}; schedule.SetRunComplete(); Assert.Equal(now, schedule.LastRunTime); } } } } ================================================ FILE: src/WebBackgrounder.UnitTests/SchedulerFacts.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Moq; using Xunit; namespace WebBackgrounder.UnitTests { public class SchedulerFacts { public class TheNextMethod { [Fact] public void ReturnsTheScheduleWithTheLowestRunTime() { var jobOne = new Mock(); jobOne.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(10)); var jobTwo = new Mock(); jobTwo.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(7)); var jobThree = new Mock(); jobThree.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(27)); var jobs = new[] { jobOne.Object, jobTwo.Object, jobThree.Object }; var scheduler = new Scheduler(jobs); var schedule = scheduler.Next(); Assert.Equal(jobTwo.Object, schedule.Job); } [Fact] public void ReturnsTheCorrectNextScheduleWhenRunComplete() { var startDate = DateTime.UtcNow.AddMilliseconds(-7); var dates = new Queue(new[] { startDate, startDate.AddSeconds(7), startDate.AddSeconds(10), startDate.AddSeconds(14), startDate.AddSeconds(17), }); var jobs = new[] { new WaitJob(10), new WaitJob(7), new WaitJob(17) }; var scheduler = new Scheduler(jobs, dates.Dequeue); var firstSchedule = scheduler.Next(); firstSchedule.Job.Execute(); firstSchedule.SetRunComplete(); var secondSchedule = scheduler.Next(); secondSchedule.SetRunComplete(); var thirdSchedule = scheduler.Next(); thirdSchedule.SetRunComplete(); var fourthSchedule = scheduler.Next(); fourthSchedule.SetRunComplete(); Assert.Equal(7, ((WaitJob)firstSchedule.Job).Id); Assert.Equal(10, ((WaitJob)secondSchedule.Job).Id); Assert.Equal(7, ((WaitJob)thirdSchedule.Job).Id); Assert.Equal(17, ((WaitJob)fourthSchedule.Job).Id); } // If a task takes longer than its interval, // we need to make sure it doesn't block other tasks from running. [Fact] public void TasksAreScheduledFairly() { var startDate = DateTime.UtcNow.AddMilliseconds(-7); var dates = new Queue(new[] { startDate, startDate.AddSeconds(100), startDate.AddSeconds(100), startDate.AddSeconds(140), startDate.AddSeconds(170), }); var jobs = new[] { new WaitJob(10), new WaitJob(7), new WaitJob(17) }; var scheduler = new Scheduler(jobs, dates.Dequeue); var firstSchedule = scheduler.Next(); firstSchedule.Job.Execute(); firstSchedule.SetRunComplete(); var secondSchedule = scheduler.Next(); secondSchedule.SetRunComplete(); var thirdSchedule = scheduler.Next(); thirdSchedule.SetRunComplete(); var fourthSchedule = scheduler.Next(); fourthSchedule.SetRunComplete(); Assert.Equal(7, ((WaitJob)firstSchedule.Job).Id); Assert.Equal(10, ((WaitJob)secondSchedule.Job).Id); Assert.Equal(17, ((WaitJob)thirdSchedule.Job).Id); Assert.Equal(7, ((WaitJob)fourthSchedule.Job).Id); } private class WaitJob : Job { public WaitJob(int intervalSeconds) : base("Waits", TimeSpan.FromSeconds(intervalSeconds)) { Id = intervalSeconds; } public int Id { get; private set; } public override Task Execute() { return new Task(() => Thread.Sleep(1)); } } } public class TheConstructor { [Fact] public void ThrowsExceptionWhenAnyJobHasNegativeInterval() { var job = new Mock(); job.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(1)); var negaJob = new Mock(); negaJob.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(-1)); var jobs = new[] { job.Object, negaJob.Object }; Assert.Throws(() => new Scheduler(jobs)); } } } } ================================================ FILE: src/WebBackgrounder.UnitTests/WebBackgrounder.UnitTests.csproj ================================================  Debug AnyCPU 8.0.30703 2.0 {6F6BBD68-74D1-4C32-AA9C-42B8690BA484} Library Properties WebBackgrounder.UnitTests WebBackgrounder.UnitTests v4.0 512 ..\ true false true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 ..\packages\EntityFramework.4.1.10715.0\lib\EntityFramework.dll ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll ..\packages\xunit.1.8.0.1549\lib\xunit.dll {06D8DE5D-F101-4CD5-B406-8A211216FCE1} WebBackgrounder.EntityFramework {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E} WebBackgrounder ================================================ FILE: src/WebBackgrounder.UnitTests/WebFarmJobCoordinatorFacts.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Moq; using Xunit; namespace WebBackgrounder.UnitTests { public class WebFarmJobCoordinatorFacts { public class TheReserveWorkMethod { [Fact] public void ReturnsNullWhenActiveWorkersExistAndIsNotTimedOut() { var job = new FakeJob { Name = "jobname", Timeout = TimeSpan.MaxValue }; var repository = new Mock(); repository.Setup(r => r.GetLastWorkItem(job)).Returns(new Mock().Object); var coordinator = new WebFarmJobCoordinator(repository.Object); var unitOfWork = coordinator.ReserveWork("worker-id", job); Assert.Null(unitOfWork); } [Fact] public void ReturnsNullWhenActiveWorkersExistWithinTransaction() { var job = new FakeJob { Name = "jobname", Timeout = TimeSpan.MaxValue }; var complete = new Mock(); complete.Setup(wi => wi.Completed).Returns(DateTime.UtcNow); var active = new Mock(); active.Setup(wi => wi.Completed).Returns((DateTime?)null); var activeWorkerQueue = new Queue(new[] { complete.Object, active.Object }); var repository = new Mock(); repository.Setup(r => r.GetLastWorkItem(job)).Returns(activeWorkerQueue.Dequeue); repository.Setup(r => r.RunInTransaction(It.IsAny())).Callback(a => a()); repository.Setup(r => r.CreateWorkItem("worker-id", job)).Throws(new InvalidOperationException()); var coordinator = new WebFarmJobCoordinator(repository.Object); var unitOfWork = coordinator.ReserveWork("worker-id", job); Assert.Null(unitOfWork); } [Fact] public void ReturnsUnitOfWorkWhenNoActiveWorkers() { var job = new FakeJob { Name = "jobname" }; var repository = new Mock(); repository.Setup(r => r.GetLastWorkItem(job)).Returns((IWorkItem)null); repository.Setup(r => r.RunInTransaction(It.IsAny())).Callback(a => a()); repository.Setup(r => r.CreateWorkItem("worker-id", job)).Returns(123); var coordinator = new WebFarmJobCoordinator(repository.Object); var unitOfWork = coordinator.ReserveWork("worker-id", job); Assert.NotNull(unitOfWork); } [Fact] public void ReturnsUnitOfWorkWhenLastActiveWorkerIsTimedOut() { var job = new FakeJob { Name = "jobname", Timeout = TimeSpan.FromSeconds(10) }; var workItem = new Mock(); workItem.Setup(w => w.Id).Returns(1233); workItem.Setup(w => w.Started).Returns(DateTime.UtcNow.AddSeconds(-11)); var repository = new Mock(); repository.Setup(r => r.GetLastWorkItem(job)).Returns(workItem.Object); repository.Setup(r => r.RunInTransaction(It.IsAny())).Callback(a => a()); repository.Setup(r => r.CreateWorkItem("worker-id", job)).Returns(123); var coordinator = new WebFarmJobCoordinator(repository.Object); var unitOfWork = coordinator.ReserveWork("worker-id", job); Assert.NotNull(unitOfWork); repository.Verify(r => r.SetWorkItemFailed(1233, It.IsAny())); } } public class TheGetWorkMethod { [Fact] public void WithJobThatThrowsExceptionWhenCreatingTaskStillReturnsTask() { var job = new Mock(); job.Setup(j => j.Name).Returns("job-name"); job.Setup(j => j.Execute()).Throws(new InvalidOperationException("Test Exception")); var repository = new Mock(); repository.Setup(r => r.GetLastWorkItem(job.Object)).Returns((IWorkItem)null); repository.Setup(r => r.RunInTransaction(It.IsAny())).Callback(a => a()); repository.Setup(r => r.CreateWorkItem("worker-id", job.Object)).Returns(() => 123); var coordinator = new WebFarmJobCoordinator(repository.Object); var task = coordinator.GetWork(job.Object); Assert.NotNull(task); } [Fact] public void WithNullJobThrowsArgumentNullException() { var repository = new Mock(); var coordinator = new WebFarmJobCoordinator(repository.Object); var exception = Assert.Throws(() => coordinator.GetWork(null)); Assert.Equal("job", exception.ParamName); } } public class TheCtor { [Fact] public void WithNullWorkItemRepositoryThrowsArgumentNullException() { var exception = Assert.Throws(() => new WebFarmJobCoordinator(null)); Assert.Equal("workItemRepository", exception.ParamName); } } class FakeJob : IJob { public string Name { get; set; } public Task Execute() { return null; } public TimeSpan Interval { get; set; } public TimeSpan Timeout { get; set; } } } } ================================================ FILE: src/WebBackgrounder.UnitTests/WorkItemCleanupJobFacts.cs ================================================ using System; using System.Linq; using Moq; using WebBackgrounder.Jobs; using Xunit; namespace WebBackgrounder.UnitTests { public class WorkItemCleanupJobFacts { public class TheExecuteMethod { [Fact] public void DeletesItemsOlderThanSpecifiedTimeSpan() { var context = new Mock(); context.Setup(c => c.SaveChanges()).Verifiable(); context.Setup(c => c.WorkItems).Returns(new InMemoryDbSet { new WorkItem {Id = 101, Completed = DateTime.UtcNow.AddDays(-4)}, new WorkItem {Id = 102, Completed = DateTime.UtcNow.AddDays(-4)}, new WorkItem {Id = 103, Completed = DateTime.UtcNow.AddDays(-2).AddMilliseconds(-1)}, new WorkItem {Id = 104, Completed = DateTime.UtcNow}, new WorkItem {Id = 105 } }); var job = new WorkItemCleanupJob(TimeSpan.FromMilliseconds(1), TimeSpan.FromDays(2), context.Object); var task = job.Execute(); task.Start(); task.Wait(); Assert.Equal(2, context.Object.WorkItems.Count()); Assert.Equal(104, context.Object.WorkItems.First().Id); Assert.Equal(105, context.Object.WorkItems.ElementAt(1).Id); context.Verify(); } [Fact] public void DoesNothingWhenAllRecordsAreWithinKeepRecordsSpan() { var context = new Mock(); context.Setup(c => c.SaveChanges()).Throws( new InvalidOperationException("Should not have tried to save changes")); context.Object.WorkItems = new InMemoryDbSet { new WorkItem(), new WorkItem() }; var job = new WorkItemCleanupJob(TimeSpan.FromSeconds(1), TimeSpan.FromDays(1), context.Object); job.Execute(); } } } } ================================================ FILE: src/WebBackgrounder.UnitTests/WorkItemExtensionsFacts.cs ================================================ using System; using Moq; using Xunit; namespace WebBackgrounder.UnitTests { public class WorkItemExtensionsFacts { public class TheIsActiveMethod { [Fact] public void ReturnsTrueForIncomplete() { var workItem = new Mock(); workItem.Setup(w => w.Completed).Returns((DateTime?)null); bool active = workItem.Object.IsActive(); Assert.True(active); } [Fact] public void ReturnsFalseForComplete() { var workItem = new Mock(); workItem.Setup(w => w.Completed).Returns(DateTime.UtcNow); bool active = workItem.Object.IsActive(); Assert.False(active); } [Fact] public void ReturnsFalseForNullWorkItem() { Assert.False(((IWorkItem)null).IsActive()); } } public class TheIsTimedOutMethod { [Fact] public void ReturnsTrueWhenWorkItemIsPastJobExpirationTimespan() { var started = DateTime.UtcNow.AddSeconds(-10); var workItem = new Mock(); workItem.Setup(w => w.Started).Returns(started); var job = new Mock(); job.Setup(j => j.Timeout).Returns(TimeSpan.FromSeconds(9)); bool expired = workItem.Object.IsTimedOut(job.Object); Assert.True(expired); } [Fact] public void ReturnsFalseWhenWorkItemIsWithinJobExpirationTimespan() { var started = DateTime.UtcNow.AddSeconds(-5); var workItem = new Mock(); workItem.Setup(w => w.Started).Returns(started); var job = new Mock(); job.Setup(j => j.Timeout).Returns(TimeSpan.FromSeconds(100)); bool expired = workItem.Object.IsTimedOut(job.Object); Assert.False(expired); } [Fact] public void ReturnsFalseWhenWorkItemIsNull() { var expired = ((IWorkItem)null).IsTimedOut(new Mock().Object); Assert.False(expired); } [Fact] public void ThrowsArgumentNullExceptionWhenJobIsNull() { Assert.Throws(() => ((IWorkItem)null).IsTimedOut(null)); } } } } ================================================ FILE: src/WebBackgrounder.UnitTests/packages.config ================================================  ================================================ FILE: src/WebBackgrounder.sln ================================================  Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder.DemoWeb", "WebBackgrounder.DemoWeb\WebBackgrounder.DemoWeb.csproj", "{0A004B9F-4440-4C05-9D84-E38726D6162F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder", "WebBackgrounder\WebBackgrounder.csproj", "{0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder.UnitTests", "WebBackgrounder.UnitTests\WebBackgrounder.UnitTests.csproj", "{6F6BBD68-74D1-4C32-AA9C-42B8690BA484}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder.EntityFramework", "WebBackgrounder.EntityFramework\WebBackgrounder.EntityFramework.csproj", "{06D8DE5D-F101-4CD5-B406-8A211216FCE1}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{3F870BEB-5F40-4CC3-9226-2FBE7A930E81}" ProjectSection(SolutionItems) = preProject ..\README.md = ..\README.md EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {0A004B9F-4440-4C05-9D84-E38726D6162F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0A004B9F-4440-4C05-9D84-E38726D6162F}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A004B9F-4440-4C05-9D84-E38726D6162F}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A004B9F-4440-4C05-9D84-E38726D6162F}.Release|Any CPU.Build.0 = Release|Any CPU {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}.Debug|Any CPU.Build.0 = Debug|Any CPU {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}.Release|Any CPU.ActiveCfg = Release|Any CPU {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}.Release|Any CPU.Build.0 = Release|Any CPU {6F6BBD68-74D1-4C32-AA9C-42B8690BA484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6F6BBD68-74D1-4C32-AA9C-42B8690BA484}.Debug|Any CPU.Build.0 = Debug|Any CPU {6F6BBD68-74D1-4C32-AA9C-42B8690BA484}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F6BBD68-74D1-4C32-AA9C-42B8690BA484}.Release|Any CPU.Build.0 = Release|Any CPU {06D8DE5D-F101-4CD5-B406-8A211216FCE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {06D8DE5D-F101-4CD5-B406-8A211216FCE1}.Debug|Any CPU.Build.0 = Debug|Any CPU {06D8DE5D-F101-4CD5-B406-8A211216FCE1}.Release|Any CPU.ActiveCfg = Release|Any CPU {06D8DE5D-F101-4CD5-B406-8A211216FCE1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal