public ExecutionResult Execute(JobClientInterface jci) { ExecutionResult er = new ExecutionResult(); er.Success = jci.GetPropertyValue("Mock_ReportSuccess").ToLower() == "true"; string toLog = jci.GetPropertyValue("Mock_StringToLog"); if (toLog != null) { jci.LogString(toLog); } string throwException = jci.GetPropertyValue("Mock_ThrowException"); if (throwException != null) { throw new Exception(throwException); } return er; }
public ExecutionResult Execute(JobClientInterface jci) { jci.LogString("AppDomain: " + AppDomain.CurrentDomain.FriendlyName); NUnit.Framework.Assert.IsTrue(true); ExecutionResult er = new ExecutionResult(); er.Success = jci.GetPropertyValue("Mock_ReportSuccess").ToLower() == "true"; string toLog = jci.GetPropertyValue("Mock_StringToLog"); if (toLog != null) { jci.LogString(toLog); } string throwException = jci.GetPropertyValue("Mock_ThrowException"); if (throwException != null) { throw new Exception(throwException); } return er; }
public ExecutionResult Execute(JobClientInterface jci) { jci.ShutdownOnCompletion = true; WindowsUpdateHelper.WindowsUpdateResult updateResult; try { updateResult = WindowsUpdateHelper.InstallUpdates(jci); } catch (Exception ex) { jci.ShutdownOnCompletion = false; ExecutionResult jr = new ExecutionResult(ex.ToString(), null); return jr; } if (updateResult.RestartRequired) { jci.SetPropertyValue("updatesApplied", "true"); jci.StartupOnNextRun(); System.Diagnostics.Process.Start("shutdown", "-r"); return null; } else { //reset the autostart functunality for jobmanagerclient FileInfo regFile = new FileInfo(Path.Combine(jci.WorkingDir.FullName, "..\\..\\run_once_automation.reg")); System.Diagnostics.Process.Start("regedit", "/s \"" + regFile.FullName + "\""); ExecutionResult jr = new ExecutionResult(); //if updates were applied, instruct the framework to take a snapshot of the VM after shutdown string updatesAppliedProperty = jci.GetPropertyValue("updatesApplied"); bool updatesAppliedBeforeReboot = updatesAppliedProperty != null && updatesAppliedProperty == "true"; if (updateResult.UpdatesApplied || updatesAppliedBeforeReboot) { jr.SnapshotOnShutdown = true; jr.SnapshotDesc = "WU Applied"; } jr.Success = true; return jr; } }
public ExecutionResult Execute(JobClientInterface jci) { ExecutionResult er = new ExecutionResult(); er.Success = true; if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "WindowsPowerShell"))) { int major = Environment.OSVersion.Version.Major; int minor = Environment.OSVersion.Version.Minor; string installerToRun = null; WINDOWS_OS? os = GetOS(); if (os != null) { switch (os.Value) { case WINDOWS_OS.WindowsXP: if (ArchitectureIs64Bit) { installerToRun = "PS_WindowsServer2003.WindowsXP-KB926139-v2-x64-ENU.exe"; } else { installerToRun = "PS_WindowsXP-KB926139-v2-x86-ENU.exe"; } break; case WINDOWS_OS.WindowsVista: if (ArchitectureIs64Bit) { installerToRun = "PS_Vista_Windows6.0-KB928439-x64.msu"; } else { installerToRun = "PS_Vista_Windows6.0-KB928439-x86.msu"; } break; case WINDOWS_OS.Windows7: //no need break; case WINDOWS_OS.WindowsServer2003: if (ArchitectureIs64Bit) { installerToRun = "PS_WindowsServer2003.WindowsXP-KB926139-v2-x64-ENU.exe"; } else { installerToRun = "PS_WindowsServer2003-KB926139-v2-x86-ENU.exe"; } break; case WINDOWS_OS.WindowsServer2008: jci.LogString("Installing PowerShell for Server 2008"); string pkgmgrPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "pkgmgr.exe"); string pkgmgr64Path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%\\sysnative"), "pkgmgr.exe"); if (File.Exists(pkgmgr64Path)) { pkgmgrPath = pkgmgr64Path; } UtilityBackgroundProcess proc = new UtilityBackgroundProcess(pkgmgrPath); proc.DebugMessageSent += new DebugMessageHandler(delegate(object sender, string message) { jci.LogString(message); }); if (proc.Run("/iu:MicrosoftWindowsPowerShell")) { if (proc.WaitForExit(TimeSpan.FromMinutes(45))) { int exitCode = proc.ExitCode.Value; if (exitCode != 0) { er.Success = false; er.Errors.Add("Exit Code for pkmgr.exe was non-zero: " + exitCode); } } else { er.Success = false; er.Errors.Add("pkmgr.exe timedout and was terminated."); } } else { er.Success = false; er.Errors.Add("pkmgr.exe could not be started, or terminated immediatly"); } jci.LogString("Done Installing PowerShell for Server 2008"); break; case WINDOWS_OS.WindowsServer2008R2: //no need break; } } if (installerToRun != null) { installerToRun = Path.Combine(jci.WorkingDir.FullName, installerToRun); UtilityBackgroundProcess proc; string args = String.Empty; if (installerToRun.EndsWith("msu")) { proc = new UtilityBackgroundProcess("wusa"); args = "\"" + installerToRun + "\" /quiet"; } else { proc = new UtilityBackgroundProcess(installerToRun); args = "/quiet"; } proc.DebugMessageSent += new DebugMessageHandler(delegate(object sender, string message) { jci.LogString(message); }); proc.Run(args); if (proc.WaitForExit(TimeSpan.FromMinutes(30))) { if (proc.ExitCode != null) { if (proc.ExitCode.Value == 0) { proc.WaitForSubProcessToExit(); } else { er.Success = false; string message = "The process " + proc.ProgramEXE + " returned a non-zero exit code: " + proc.ExitCode.Value; er.Errors.Add(message); jci.LogString(message); } } else { er.Success = false; string message = "The process " + proc.ProgramEXE + " did not return an exit code."; er.Errors.Add(message); jci.LogString(message); } } else { er.Success = false; string message = "The process " + proc.ProgramEXE + " timed out after 30 minutes and was terminated."; er.Errors.Add(message); jci.LogString(message); } } } return er; }
public static ExecutionResult RunTests(JobClientInterface jci) { int testTimeoutMin = 30; string testTimeoutPropValue = jci.GetPropertyValue("testTimeoutMin"); if (testTimeoutPropValue != null) { Int32.TryParse(testTimeoutPropValue, out testTimeoutMin); } TimeSpan testingTimeout = TimeSpan.FromMinutes(testTimeoutMin); DirectoryInfo workingDir = jci.WorkingDir; ExecutionResult jr = new ExecutionResult(); jr.Success = true; string testDLLArg = String.Empty; foreach (string testDLLRaw in jci.GetPropertyValue("Tests").Split(';')) { string testDLL = testDLLRaw.Trim(); FileInfo testDLLPath = new FileInfo(Path.Combine(workingDir.FullName, testDLL)); testDLL = testDLLPath.Name; if (!testDLLPath.Exists) { jr.Success = false; jr.Errors.Add("Could not find test dll \"" + testDLLPath.FullName + "\". File does not exist."); continue; } testDLLArg += "\"" + testDLLPath.FullName + "\" "; //look for any .config key/value pairs that we need to swap out Dictionary<string, string> configFileProperties = new Dictionary<string, string>(); Dictionary<string, string> endPointAddresses = new Dictionary<string, string>(); Dictionary<string, string> connectionStrings = new Dictionary<string, string>(); string appConfigToken = testDLL + "_"; string endpointToken = testDLL + "-endpoint-"; string connStringToken = testDLL + "-connstring-"; foreach (string key in jci.RunningJob.Properties.Keys) { if (key.StartsWith(appConfigToken)) { string actualKey = key.Substring(appConfigToken.Length, key.Length - appConfigToken.Length); string value = jci.GetPropertyValue(key); configFileProperties[actualKey] = value; } else if (key.StartsWith(endpointToken)) { string endPointContract = key.Substring(endpointToken.Length, key.Length - endpointToken.Length); string address = jci.GetPropertyValue(key); endPointAddresses[endPointContract] = address; } else if (key.StartsWith(connStringToken)) { string connStringName = key.Substring(connStringToken.Length, key.Length - connStringToken.Length); string value = jci.GetPropertyValue(key); connectionStrings[connStringName] = value; } } FileInfo configFile = new FileInfo(testDLLPath.FullName + ".config"); if (configFile.Exists && (configFileProperties.Count > 0 || endPointAddresses.Count > 0 || connectionStrings.Count > 0)) { var config = ConfigurationManager.OpenExeConfiguration(testDLLPath.FullName); if (configFileProperties.Count > 0) { NameValueCollection currentProperties = new NameValueCollection(); foreach (KeyValueConfigurationElement ele in config.AppSettings.Settings) { currentProperties[ele.Key] = ele.Value; } foreach (string key in configFileProperties.Keys) { currentProperties[key] = configFileProperties[key]; } config.AppSettings.Settings.Clear(); foreach (string key in currentProperties.Keys) { config.AppSettings.Settings.Add(key, currentProperties[key]); } } if (endPointAddresses.Count > 0) { var serviceModelGroup = System.ServiceModel.Configuration.ServiceModelSectionGroup.GetSectionGroup(config); foreach (System.ServiceModel.Configuration.ChannelEndpointElement endpointElement in serviceModelGroup.Client.Endpoints) { string contract = endpointElement.Contract; if (endPointAddresses.ContainsKey(contract)) { endpointElement.Address = new Uri(endPointAddresses[contract]); } } } if (connectionStrings.Count > 0) { var connStringSection = config.ConnectionStrings; foreach (string connString in connectionStrings.Keys) { var connStringSettings = connStringSection.ConnectionStrings[connString]; if (connStringSettings != null) { connStringSettings.ConnectionString = connectionStrings[connString]; } } } config.Save(ConfigurationSaveMode.Modified); } } string xmlOutputPath = Path.Combine(workingDir.FullName, "results.nur"); string xsltPath = Path.Combine(workingDir.FullName, "HTMLDetail.xslt"); List<string> categoriesToRun = new List<string>(); List<string> categoriesToExlcude = new List<string>(); string packageDefinedCategories = jci.GetPropertyValue("Package_NUnitCategories"); string jobDefinedCategories = jci.GetPropertyValue("Job_NUnitCategories"); if(IsTrue(jci.GetPropertyValue("ExcludeJobCategories"))) { jobDefinedCategories = null; } string configDefinedCategories = jci.GetPropertyValue("Config_NUnitCategories"); if (IsTrue(jci.GetPropertyValue("ExcludeConfigurationCategories"))) { configDefinedCategories = null; } string excludeCategories = jci.GetPropertyValue("Package_Exclude_NUnitCategories"); foreach (string categoriesStr in new string[] { packageDefinedCategories, jobDefinedCategories, configDefinedCategories }) { if (categoriesStr != null) { string[] categories = categoriesStr.Split(';'); foreach (string category in categories) { categoriesToRun.Add(category); } } } if (excludeCategories != null) { string[] categories = excludeCategories.Split(';'); foreach (string category in categories) { categoriesToExlcude.Add(category); } } string nUnitConsoleArgs = testDLLArg + " /xml=\"" + xmlOutputPath + "\" /noshadow"; if (categoriesToRun.Count > 0) { nUnitConsoleArgs += " /include:" + String.Join(",", categoriesToRun.ToArray()); } if (categoriesToExlcude.Count > 0) { nUnitConsoleArgs += " /exclude:" + String.Join(",", categoriesToExlcude.ToArray()); } string nunitExe = Path.Combine(workingDir.FullName, "nunit-console-x86.exe"); string nunitAgentExe = Path.Combine(workingDir.FullName, "nunit-agent-x86.exe"); //put in exception for program in windows firewall //netsh.exe firewall set allowedprogram program = "path\to\program" name = "appName" mode = ENABLE UtilityBackgroundProcess netshProc = new UtilityBackgroundProcess("netsh.exe"); netshProc.DebugMessageSent += new DebugMessageHandler(delegate(object sender, string message) { jci.LogString(message); }); netshProc.Run("firewall set allowedprogram program = \"" + nunitAgentExe + "\" name = \"NUnit Agent Proc\" mode = ENABLE"); netshProc.WaitForExit(); jci.LogString("Running Unit Tests from \"" + testDLLArg + "\" and outputing to \"" + xmlOutputPath + "\""); int retryCount = 0; UtilityBackgroundProcess p = null; while (retryCount < 2) { p = new UtilityBackgroundProcess(nunitExe); p.DebugMessageSent += new DebugMessageHandler(delegate(object sender, string message) { jci.LogString(message); }); p.Run(nUnitConsoleArgs); if (!p.WaitForExit(testingTimeout)) { jci.LogString("nunit-console-x86.exe ran for longer than the timeout of " + testingTimeout.TotalMinutes + " minutes and was stopped."); } if (p.ExitCode < 0 && p.StandardError.Contains("Unable to locate fixture ")) { jci.LogString("nunit failed with \"Unable to locate fixture\", runing nunit again..."); retryCount++; } else { break; } } string nunitOutput = p.StandardOutput; FileData nunitConsoleOutput = new FileData(); nunitConsoleOutput.Name = "nunit.log"; nunitConsoleOutput.Data = Encoding.UTF8.GetBytes(nunitOutput); jr.Attachments.Add(nunitConsoleOutput); string nunitErr = p.StandardError; FileData nunitErrOutput = new FileData(); nunitErrOutput.Name = "nunit.err.log"; nunitErrOutput.Data = Encoding.UTF8.GetBytes(nunitErr); jr.Attachments.Add(nunitErrOutput); Regex regex = new Regex(@"Tests run:\s+\d+,\s+Errors:\s+(\d+),\s+Failures:\s+(\d+)"); Match m = regex.Match(nunitOutput); bool nunitPassed = m.Success && m.Groups.Count > 2 && m.Groups[1].Value == "0" && m.Groups[2].Value == "0"; jci.LogString("Done Running Unit Tests. Result: " + (nunitPassed ? "Success" : "Failure")); if (File.Exists(xmlOutputPath)) { using (Stream resultsSummary = TransformXML(xmlOutputPath, Path.Combine(workingDir.FullName, xsltPath))) { using (TextReader tr = new StreamReader(resultsSummary)) { List<FileData> attachments = new List<FileData>(); attachments.Add(FileData.FromFile(new FileInfo(xmlOutputPath))); FileData resultFileData = new FileData(); resultFileData.Name = "result.html"; resultFileData.Data = new byte[resultsSummary.Length]; resultsSummary.Read(resultFileData.Data, 0, (int)resultsSummary.Length); attachments.Add(resultFileData); jr.Attachments.AddRange(attachments); string treatFailedTestsAsPackageFailure = jci.GetPropertyValue("TreatFailedTestsAsPackageFailure"); if (!nunitPassed && treatFailedTestsAsPackageFailure != null && treatFailedTestsAsPackageFailure.ToLower() == "true") { jr.Errors.Add("Nunit tests did not pass!"); jr.Success = false; } } } } else { string nunitErrorPath = Path.Combine(workingDir.FullName, "nunit_error.png"); TakeScreenShot(nunitErrorPath); jr.Success = false; jr.Attachments.Add(FileData.FromFile(new FileInfo(nunitErrorPath))); jr.Errors.Add("Nunit tests did not run! See attached screenshot and log!"); } string shutdownStr = jci.GetPropertyValue("ShutdownAfterTests"); if (shutdownStr != null && shutdownStr.ToLower() == "true") { jci.ShutdownOnCompletion = true; } return jr; }
public void Run() { try { FileInfo mapNetBatchFile = new FileInfo(Path.Combine(Utilities.ExecutingAssembly.Directory.FullName, "map.bat")); //if the network drive is disconected, then we will be unable to get to the server inbox, in which case we should try to remap if (!AppConfig.ServerInbox.Exists && mapNetBatchFile.Exists) { using (System.Diagnostics.Process p = new System.Diagnostics.Process()) { p.StartInfo.WorkingDirectory = mapNetBatchFile.Directory.FullName; p.StartInfo.FileName = mapNetBatchFile.Name; p.StartInfo.CreateNoWindow = true; p.StartInfo.UseShellExecute = true; p.Start(); p.WaitForExit(); } } int packageToRun = 0; JobResult result = new JobResult(); result.Completed = true; //string sendQueueName = @"FormatName:DIRECT=OS:hammerbuildvm\Private$\jobmanager"; //string sendQueueName = @"FormatName:DIRECT=OS:ryanadams2\Private$\test2"; //jci.LogString("Connecting to Job Manager receive queue (" + sendQueueName + ")"); MessageSendRecieve msr = new MessageSendRecieve(AppConfig.ServerInbox, AppConfig.ServerOutbox); //jci.LogString("Permission = " + msr.RemoteMessageQueue.AccessMode.ToString()); //look for an existing job to run/continue before getting a new job from the server FileInfo jobXML = new FileInfo(Path.Combine(Utilities.ExecutingAssembly.Directory.FullName, "job.xml")); if (jobXML.Exists) { using (TextReader tr = new StreamReader(jobXML.FullName)) { j = XMLSerializable.FromXML<Job>(tr.ReadToEnd()); if (j.Properties.ContainsKey("PackageToRun")) { packageToRun = Int32.Parse(j.Properties["PackageToRun"]); } } try { //rename the job file so the next run doesn't automatically use it. The job.xml file will be put back //as part of jci.StartupOnNextRun if it is meant to be continued after a restart string lastFile = jobXML.FullName + ".old"; if(File.Exists(lastFile)) { File.Delete(lastFile); } File.Move(jobXML.FullName, lastFile); } catch (Exception ex) { //if the delete fails lets log it, but it isn't critical so let's eat the exception LogString("Could not delete existing job.xml file: " + ex.ToString()); } //look for an existing JobResult to pull in FileInfo jobResultXML = new FileInfo(Path.Combine(Utilities.ExecutingAssembly.Directory.FullName, "jobresult.xml")); if (jobResultXML.Exists) { try { using (TextReader tr = new StreamReader(jobResultXML.FullName)) { result = XMLSerializable.FromXML<JobResult>(tr.ReadToEnd()); } } catch (Exception ex) { //log, but eat it LogString(ex.ToString()); } } } else { LogString("Requesting Jobs from Job Manager"); string messageID = msr.RequestJob(); LogString("Sent request with message id: " + messageID); LogString("Waiting for Job response from Job Manager"); j = msr.WaitForJob(messageID, DEFAULT_JOB_WAIT); if (j == null) { LogString("No Jobs Available"); return; } try { LogString("Found Job: " + j.JobID); if (baseDir.Exists) { baseDir.Delete(true); //TODO wait for files to be deleted? } baseDir.Create(); List<string> keys = new List<string>(j.ISOs.Keys); foreach (string isoName in keys) { FileInfo isoPath = new FileInfo(j.ISOs[isoName]); string destPath = Path.Combine(Utilities.ExecutingAssembly.Directory.FullName, isoPath.Name); LogString("Copying ISO from \"" + isoPath.Directory.FullName + "\" to \"" + destPath + "\""); isoPath.CopyTo(destPath); j.ISOs[isoName] = destPath; } if (j.Properties == null) { j.Properties = new SerializableDictionary<string, string>(); } } catch (Exception ex) { LogString(ex.ToString()); result.Completed = false; ExecutionResult er = new ExecutionResult(ex.ToString(), null); result.ExecutionResults.Add(er); Logger.Instance.Pause(); result.Logs.Add(FileData.FromFile(new FileInfo(Logger.Instance.FileName))); Logger.Instance.Resume(); LogString("Sending Job Result"); msr.ReportJobStatus(new JobCompleted(j, result)); LogString("Job Result Sent"); return; } } if (j.Packages.Count == 0) { Logger.Instance.Pause(); result.Logs.Add(FileData.FromFile(new FileInfo(Logger.Instance.FileName))); Logger.Instance.Resume(); } while (packageToRun < j.Packages.Count) { runningPackageDir = new DirectoryInfo(Path.Combine(baseDir.FullName, packageToRun.ToString())); ExecutablePackage ep = j.Packages[packageToRun]; runningPackage = ep; ExecutionResult er = new ExecutionResult(); try { if (!ep.ContentDirectory.ToLower().Equals(runningPackageDir.FullName.ToLower())) { if (runningPackageDir.Exists) { runningPackageDir.Delete(true); } runningPackageDir.Create(); LogString("Copying data from \"" + ep.ContentDirectory + "\" to \"" + runningPackageDir.FullName + "\""); DirectoryData.FromDirectory(new DirectoryInfo(ep.ContentDirectory)).DumpContentsToDir(runningPackageDir); ep.ContentDirectory = runningPackageDir.FullName; } LogString("Loading external test DLL: " + ep.JobRunnerDLLName + " , " + ep.JobRunnerClassName); JobRunner jr = LoadJobRunner(ep.JobRunnerClassName, Path.Combine(runningPackageDir.FullName, ep.JobRunnerDLLName)); LogString("Executing Execute() method on external DLL"); er = jr.Execute(this); } catch (Exception ex) { LogString(ex.ToString()); result.Completed = false; er = new ExecutionResult(ex.ToString(), null); } Logger.Instance.Pause(); result.Logs.Add(FileData.FromFile(new FileInfo(Logger.Instance.FileName))); Logger.Instance.Resume(); if (er != null) { result.ExecutionResults.Add(er); } //lets save the current job result using (TextWriter tw = new StreamWriter(Path.Combine(Utilities.ExecutingAssembly.Directory.FullName, "jobresult.xml"), false)) { tw.Write(result.ToXML()); } if (er == null) { //The automation is likely not finished, the computer is likely going to reboot and //we want this execution to continue after reboot so we should exit now instead of going to the next package. //the executable package should have already called startuponnextrun return; } if (!er.Success) { //stop on first error break; } packageToRun++; j.Properties["PackageToRun"] = packageToRun.ToString(); if (er.Success && er.RestartAfter) { StartupOnNextRun(); LogString("Restarting ..."); system.Shutdown(true); return; } } LogString("Sending Job Result"); msr.ReportJobStatus(new JobCompleted(j, result)); LogString("Job Result Sent"); //cleanup if (File.Exists(Path.Combine(Utilities.ExecutingAssembly.Directory.FullName, "jobresult.xml"))) { File.Delete(Path.Combine(Utilities.ExecutingAssembly.Directory.FullName, "jobresult.xml")); } if (ShutdownOnCompletion) { LogString("Shuting Down ..."); system.Shutdown(false); //so, lets exit the program System.Windows.Forms.Application.Exit(); } } catch (ThreadAbortException) { //eat it, get out right away. Program is exiting or user has stopped automation return; } catch (Exception e) { LogString("Exception in thread: "+e.ToString()); return; } }
public void TestNullISOs() { sut.EmulateServiceStart(null); DirectoryInfo tempDir = new DirectoryInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tempdll")); tempDir.Create(); try { List<ExecutablePackage> packages = new List<ExecutablePackage>(); ExecutablePackage package = new ExecutablePackage("mock.xml", tempDir.FullName, "TestJobManager.dll", "TestJobManager.MockJobRunner", new SerializableDictionary<string, string>(), new SerializableDictionary<string, string>()); packages.Add(package); Job j1 = new Job(null, "MockVMConfig1", null, packages, new SerializableDictionary<string, string>()); MessageSendRecieve msr = new MessageSendRecieve(new DirectoryInfo(inboxPath), new DirectoryInfo(outboxPath)); DateTime queuedJobsDateTime = DateTime.Now; string job1msgID = msr.QueueJob(j1); //wait for job 1 to start Assert.True(WaitForVMAction(vmHash["MockVMName1"], VMActionType.Start, queuedJobsDateTime, TimeSpan.FromSeconds(5))); //send request for job 1 AutomationMessage m = new AutomationMessage(new SimpleRequest(SimpleRequests.JobRequest)); m.From = "MockVMName1"; Job j = msr.WaitForJob(msr.Send(m), DEFAULT_WAIT); Assert.That(j, Is.Not.Null); Assert.That(j.JobID, Is.EqualTo(j1.JobID)); //send finished for job 1 DateTime finishedSentDateTime = DateTime.Now; JobResult jr = new JobResult(); jr.Completed = true; ExecutionResult er = new ExecutionResult(); er.Success = true; jr.ExecutionResults.Add(er); msr.ReportJobStatus(new JobCompleted(j1, jr)); //wait for job completion JobCompleted jobCompleted = msr.WaitForJobCompletion(job1msgID, DEFAULT_WAIT); sut.EmulateServiceStop(); VerifyAppLogDoesNotContain(EventLogEntryType.Warning, testStart); VerifyAppLogDoesNotContain(EventLogEntryType.Error, testStart); Assert.That(jobCompleted, Is.Not.Null); Assert.That(jobCompleted.Job, Is.Not.Null); Assert.That(jobCompleted.Result, Is.Not.Null); Assert.That(jobCompleted.Result.Errors, Is.Empty); Assert.That(jobCompleted.Result.Success, Is.True); Assert.That(jobCompleted.Result.Completed, Is.True); } finally { tempDir.Delete(true); } }