Possibility to use Karma with TFS builds - tfs

I'm new to the Testacular(now Karma). But I found it is really powerful and great for automatic cross-browser JS testing. So I want to know if it is possible to use it as part of TFS building procedure to conduct automatic JS code unit testing? If anyone has previous experience, could you please let us know what to notice so that we are not going to take the wrong way.
Regards,
Jun

Here is my pseudo code to run the karma in TFS using C# helper class. The basic idea is:
Use C# unit test to test your js files using Karma.
Capture the output of Karma to show that in your build log.
Use separate process to run Karma.
Pack all Karma files into a zip file, extract that into temporary folder for each build, so that builds with different version of karma wouldn't conflict with each other.
Clean the temp folder after build.
-
namespace Test.Javascript.CrossBrowserTests
{
public class KarmaTestRunner : IDisposable
{
private const string KarmaPath = #".\node_modules\karma\bin\karma";
private string NodeBasePath { get; set; }
private string NodeFullPath { get { return NodeBasePath + #"\node\node.exe"; } }
private string NpmFullPath { get { return NodeBasePath + #"\node\npm.cmd"; } }
public KarmaTestRunner()
{
ExtractKarmaZip();
LinkGlobalKarma();
}
public int Execute(params string[] arguments)
{
Process consoleProcess = RunKarma(arguments);
return consoleProcess.ExitCode;
}
public void Dispose()
{
UnlinkGlobalKarma();
RemoveTempKarmaFiles();
}
private void ExtractKarmaZip()
{
NodeBasePath = Path.GetTempPath() + Path.GetRandomFileName();
byte[] resourceBytes = Assembly.GetExecutingAssembly().GetEmbeddedResourceBytes(typeof(KarmaTestRunner).Namespace + "." + "karma0.9.4.zip");
ZipFile file = ZipFile.Read(resourceBytes);
file.ExtractAll(NodeBasePath);
}
private void LinkGlobalKarma()
{
ExecuteConsoleProcess(NpmFullPath, "link", "karma");
}
private Process RunKarma(IEnumerable<string> arguments)
{
return ExecuteConsoleProcess(NodeFullPath, new[] { KarmaPath }.Concat(arguments).ToArray());
}
private static Process ExecuteConsoleProcess(string path, params string[] arguments)
{
//Create a process to run karma with arguments
//Hook up the OutputDataReceived envent handler on the process
}
static void OnOutputLineReceived(string message)
{
if (message != null)
Console.WriteLine(message);
}
private void UnlinkGlobalKarma()
{
ExecuteConsoleProcess(NpmFullPath, "uninstall", "karma");
}
private void RemoveTempKarmaFiles()
{
Directory.Delete(NodeBasePath, true);
}
}
}
Then use it like this:
namespace Test.Javascript.CrossBrowserTests
{
[TestClass]
public class CrossBrowserJSUnitTests
{
[TestMethod]
public void JavascriptTestsPassForAllBrowsers()
{
using (KarmaTestRunner karmaRunner = new KarmaTestRunner())
{
int exitCode = karmaRunner.Execute("start", #".\Test.Project\Javascript\Karma\karma.conf.js");
exitCode.ShouldBe(0);
}
}
}
}

A lot has changed since the original question and answer.
However, we've gotten Karma to run in our TFS build by running a Grunt task (I'm sure the same is possible with Gulp/whatever task runner you have). We were using C# before, but recently changed.
Have a grunt build task run.
Add a Grunt task after that
point the file path to your gruntfile.js and run your test task. This task will run karma:single. The grunt-cli location may be node_modules/grunt-cli/bin/grunt.
grunt.registerTask('test', [
'karma:single'
]);
Add a Publish Test Results step. Test Results Files = **/*.trx
More information about publishing Karma Test Results

Related

Jenkins API to retrieve a build log in chunks

For a custom monitoring tool I need an API (REST) to fetch the console log of a Jenkins build in chunks.
I know about the /consoleText and /logText/progressive{Text|HTML} APIs, but the problem with this is that sometimes, our build logs get really huge (up to a few GB). I have not found any way using those existing APIs that avoids fetching and transferring the whole log in one piece. This then normally drives the Jenkins master out of memory.
I already have the Java code to efficiently fetch chunks from a file, and I have a basic Jenkins plugin that gets loaded correctly.
What I'm missing is the correct extension point so that I could call my plugin via REST, for example like
http://.../jenkins/job/<jobname>/<buildnr>/myPlugin/logChunk?start=1000&size=1000
Or also, if that is easier
http://.../jenkins/myPlugin/logChunk?start=1000&size=1000&job=<jobName>&build=<buildNr>
I tried to register my plugin with something like (that code below does not work!!)
#Extension
public class JobLogReaderAPI extends TransientActionFactory<T> implements Action {
public void doLogChunk(StaplerRequest req, StaplerResponse rsp) throws IOException {
LOGGER.log(Level.INFO, "## doLogFragment req: {}", req);
LOGGER.log(Level.INFO, "## doLogFragment rsp: {}", rsp);
}
But I failed to find the right encantation to register my plugin action.
Any tips or pointers to existing plugins where I can check how to register this?
This was indeed more simple than I expected :-) It as always: once one understands the plugin system, it just needs a few lines of code.
Turns out all I needed to do was write 2 very simple classes
The "action factory" that get's called by Jenkins and registers an action on the object in question (in my case a "build" or "run"
public class ActionFactory extends TransientBuildActionFactory {
public Collection<? extends Action> createFor(Run target) {
ArrayList<Action> actions = new ArrayList<Action>();
if (target.getLogFile().exists()) {
LogChunkReader newAction = new LogChunkReader(target);
actions.add(newAction);
}
return actions;
}
The class the implements the logic
public class LogChunkReader implements Action {
private Run build;
public LogChunkReader(Run build) {
this.build = build;
}
public String getIconFileName() {
return null;
}
public String getDisplayName() {
return null;
}
public String getUrlName() {
return "logChunk";
}
public Run getBuild() {
return build;
}
public void doReadChunk(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {

How do i generate a junit report with fail

I have junit testscript that creates different and unique ID. So when it finds an existing ID or a wrong Id I want the test script report via ANT to show that it is failed for following record but passed for the rest of the records that are correct.
#Test
public void testCreateTrade() throws Exception
driver.findElement(By.id("VIN")).clear();
driver.findElement(By.id("VIN")).sendKeys(vVin);
String str = driver.getCurrentUrl();
if(str.contains("step1")) // for existing ID
{
driver.findElement(By.cssSelector("body > div.bootbox.modal.in > div.modal-footer > a.btn.null")).click();
break;
}
driver.findElement(By.id("mileage")).sendKeys(vMileage);
driver.findElement(By.id("odometerType")).sendKeys(vKm);
driver.findElement(By.id("passengers")).sendKeys(vPassengers);
driver.findElement(By.id("exteriorColor")).sendKeys(vExterior);
driver.findElement(By.id("interiorColor")).sendKeys(vInterior);
driver.findElement(By.id("hasAccident")).sendKeys(vAccident);
driver.findElement(By.id("dealerSalesPerson")).sendKeys(vSalesPerson);
driver.findElement(By.id("step3btn")).click();
Thread.sleep(1000);
String str3 = driver.getCurrentUrl();
if(str3.contains("step2")) // Loop for wrong ID
{
driver.findElement(By.linkText("Create")).click();
driver.findElement(By.xpath("html/body/div[7]/div[2]/a[1]")).click();
//System.out.println("Is a wrong Vin"+vVin);
break;
}
driver.findElement(By.id("step4btn")).click();
driver.findElement(By.id("windshieldCondition")).sendKeys(vWindshield);
driver.findElement(By.id("tireCondition")).sendKeys(vTire);
driver.findElement(By.id("accidentBrand3")).sendKeys(vAcBrand);
driver.findElement(By.id("confirmedParked")).click();
If you want a single test case to continue running after it "fails" and then report its exceptions at the end, use ErrorCollector.
#RunWith(JUnit4.class) public class YourTestClass {
#Rule public ErrorCollector errorCollector = new ErrorCollector();
#Test public void yourTest() {
// ... (your setup)
for (Record record : expectedRecords) {
if (dataSource.hasRecord(record.getId())) {
Record fetchedRecord = dataSource.getRecord(record.getId());
errorCollector.checkThat(record, matchesRecordValuesOf(record));
} else {
errorCollector.addError(new IllegalStateException(""));
}
}
}
}
Note, however, that it's always preferable to test exactly one thing per unit test. Here it may make sense, but don't overuse ErrorCollector where refactoring and splitting the test makes more sense.

Unit testing a MVC controller

I'm trying to figure out the best way to build my unit tests for an MVC app. I created a simple model and interface, which is used by the controller constructors so that the testing framework (Nsubstitute) can pass a mocked version of the repository. This test passes, as expected.
My problem is now I want to take this a step further and test the file I/O operations in the "real" instantiation of IHomeRepository. This implementation should read a value from a file in the App_Data directory.
I've tried building a test without passing a mocked version of IHomeRepsotory in, however HttpContext.Current is null when I run my test.
Do I need to mock HttpContext? Am I even going about this in the right way?
//The model
public class VersionModel
{
public String BuildNumber { get; set; }
}
//Interface defining the repository
public interface IHomeRepository
{
VersionModel Version { get; }
}
//define the controller so the unit testing framework can pass in a mocked reposiotry. The default constructor creates a real repository
public class HomeController : Controller
{
public IHomeRepository HomeRepository;
public HomeController()
{
HomeRepository = new HomeRepoRepository();
}
public HomeController(IHomeRepository homeRepository)
{
HomeRepository = homeRepository;
}
.
.
.
}
class HomeRepoRepository : IHomeRepository
{
private VersionModel _version;
VersionModel IHomeRepository.Version
{
get
{
if (_version == null)
{
var absoluteFileLocation = HttpContext.Current.Server.MapPath("~/App_Data/repo.txt");
if (absoluteFileLocation != null)
{
_version = new VersionModel() //read the values from file (not shown here)
{
BuildNumber = "value from file",
};
}
else
{
throw new Exception("path is null");
}
}
return _version;
}
}
}
[Fact]
public void Version()
{
// Arrange
var repo = Substitute.For<IHomeRepository>(); //using Nsubstitute, but could be any mock framework
repo.Version.Returns(new VersionModel
{
BuildNumber = "1.2.3.4",
});
HomeController controller = new HomeController(repo); //pass in the mocked repository
// Act
ViewResult result = controller.Version() as ViewResult;
var m = (VersionModel)result.Model;
// Assert
Assert.True(!string.IsNullOrEmpty(m.Changeset));
}
I believe you want test the real instantiation of IHomeRepository, which connects to a real database. In that case you need an App.config file, which specify the connection string. This is not a Unit test and it would an Integration Test. With HttpContext being null, you still can fake the HttpContext, retrieve real data from the database. See also here.

Looking for a build activity which breaks a build when new warnings are introduced

We're attempting to clean up a big bunch of brown field code, while at the same time a team is adding new functionality. We'd like to make sure changed and new code is cleaned from any compiler/code analysis or other warnings, but there's too many of them to begin by cleaning up the current solution.
We're using TFS 2010.
So the following was proposed:
Write/select a build activity which compares the list of warnings in the build against the lines of code that changed with that check-in.
If the warning provides a line number, and that line number was changed, fail the build.
I understand this will not find all new warnings and things introduced in other parts of the code will not be flagged, but it's at least something.
Another option that was proposed:
Compare the list of warnings of the previous known good build against the list of this build. If there are new warnings (track on file name level), fail the build.
Any known Actions out there that might provide said functionality?
Any similar Actions that can act on Code Coverage reports?
This following activity is just a basic approach, that returns false if your current build has less or equal warnings than your last build and true if they have risen.Another activity that can locate new warnings and/or present with their location in code would clearly be superior, yet I thought this might be an interesting startpoint:
using System;
using System.Activities;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
namespace CheckWarnings
{
[BuildActivity(HostEnvironmentOption.Agent)]
public sealed class CheckWarnings : CodeActivity<bool>
{
[RequiredArgument]
public InArgument<IBuildDetail> CurrentBuild { get; set; } //buildDetail
public InArgument<string> Configuration { get; set; } //platformConfiguration.Configuration
public InArgument<string> Platform { get; set; } //platformConfiguration.Platform
protected override bool Execute(CodeActivityContext context)
{
IBuildDetail currentBuildDetail = context.GetValue(CurrentBuild);
string currentConfiguration = context.GetValue(Configuration);
string currentPlatform = context.GetValue(Platform);
Uri lastKnownGoodBuildUri = currentBuildDetail.BuildDefinition.LastGoodBuildUri;
IBuildDetail lastKnownGoodBuild = currentBuildDetail.BuildServer.GetBuild(lastKnownGoodBuildUri);
int numOfCurrentWarnings = GetNumberOfWarnings(currentBuildDetail, currentConfiguration, currentPlatform);
context.TrackBuildMessage("Current compile presents " + numOfCurrentWarnings + " warnings.", BuildMessageImportance.Normal);
int numOfLastGoodBuildWarnings = GetNumberOfWarnings(lastKnownGoodBuild, currentConfiguration,
currentPlatform);
context.TrackBuildMessage("Equivalent last good build compile presents " + numOfLastGoodBuildWarnings + " warnings.", BuildMessageImportance.Normal);
if (numOfLastGoodBuildWarnings < numOfCurrentWarnings)
{
return true;
}
return false;
}
private static int GetNumberOfWarnings(IBuildDetail buildDetail, string configuration, string platform)
{
var buildInformationNodes =
buildDetail.Information.GetNodesByType("ConfigurationSummary");
foreach (var buildInformationNode in buildInformationNodes)
{
string localPlatform, numOfWarnings;
string localConfiguration = localPlatform = numOfWarnings = "";
foreach (var field in buildInformationNode.Fields)
{
if (field.Key == "Flavor")
{
localConfiguration = field.Value;
}
if (field.Key == "Platform")
{
localPlatform = field.Value;
}
if (field.Key == "TotalCompilationWarnings")
{
numOfWarnings = field.Value;
}
}
if(localConfiguration == configuration && localPlatform == platform)
{
return Convert.ToInt32((numOfWarnings));
}
}
return 0;
}
}
}
Note that this activity doesn't provide with exception handling and should further be refined, in case your build definitions build more than one solutions.It takes three input args (buildDetail, platformConfiguration.Configuration and platformConfiguration.Platform) and should be placed directly after the Run MSBuild activity.

installutil bindingRedirect

I have a windows service that depends on a 3:rd party API
The API is already installed in the GAC on the client computer
There are several versions of the API (1.0.0.0, 1.1.0.0 etc)
My service works with all versions of the API
I use a bindingRedirect tag in the app.config file which works fine when running the service.
Problem is that the app.config file is not used when running InstallUtil so I get a binding exception when registering the service.
Currently I use "sc create" to manually register the service but is there a better way?
(without editing machine.config etc)
I just ran in to this, the only solution I could find is from https://connect.microsoft.com/VisualStudio/feedback/details/525564/installutil-exe-does-not-honor-app-config-especially-binding-information:
As a workaround, you might be able to make this work by modifying the InstallUtil.exe.config file to contain the binding information. The InstallUtil.exe.config is installed to %WinDir%\Microsoft.NET\Framework\\InstallUtil.exe.config where is the version of framework you're using.
I came up with another workaround to install service with binding redirects. As I have a lot of services, this is what I decided to go after.
Change Windows installer to Console app and implement functionality to self install (using command line and ManagedInstallerClass.InstallHelper).
Implement an installer class capable of executing command line in a completely separate assembly, for example CommandLineInstaller.DLL. CommandLineInstaller.DLL shall implement methods Install/Uninstall/Rollback identically - execute a command line with parameters such as:
FileName, WorkingDirectory, Args, WindowStyle.
Modify setup project to deploy both 1) service and b) CommandLineInstaller.DLL
Modify setup project custom actions: instead of running actions of service, run actions of CommandLineInstaller.DLL. CustomActionData property for Install action will look like:
/FileName="[TARGETDIR]MyService.exe" /Args="/install" WindowStyle="Hidden"
Action configuration:
Install: myservice /install
Rollback: myservice /uninstall
Uninstall: myservice /uninstall
No need to write Commit, AFAIK.
Now, setup project will execute CommandLineInstaller.DLL installer in its own process. And then CommandLineInstaller.DLL will in turn launch MyService.exe in its own process with bloody binding redirects as they should be.
PS MyService.exe can use exit code mechanism to inform installer about failures and I highly recommend checking them from CommandLineInstaller.
Hopefully it's a good enough outline.
PS Be mindful of TARGETDIR needs to have a slash when passed by itself to directories:
/WorkDir="[TARGETDIR]\"
Example of Install CustomActionData:
/FileName="[TARGETDIR]\MyService.exe" /Args="/install" /WorkingDir="[TARGETDIR]\" /ValidExitCode="0" /WindowStyle="Normal"
Some code:
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
namespace QT.Install
{
[RunInstaller(true)]
public partial class ExecuteCommandInstaller : System.Configuration.Install.Installer
{
public class CommandArgs
{
public string FileName { get; set; }
public string WorkingDir { get; set; }
public string Args { get; set; }
public string ValidExitCode { get; set; }
public ProcessWindowStyle WindowStyle { get; set; }
}
public ExecuteCommandInstaller()
{
InitializeComponent();
}
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
ExecuteCommand(stateSaver);
}
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
ExecuteCommand(savedState);
}
public override void Uninstall(IDictionary savedState)
{
base.Uninstall(savedState);
ExecuteCommand(savedState);
}
public override void Rollback(IDictionary savedState)
{
base.Rollback(savedState);
ExecuteCommand(savedState);
}
private void ExecuteCommand(IDictionary stateSaver)
{
CommandArgs commandArgs = new CommandArgs()
{
FileName = StripDoubleSlash(Context.Parameters["FileName"] ?? ""),
WorkingDir = StripDoubleSlash(Context.Parameters["WorkingDir"] ?? ""),
Args = Context.Parameters["Args"] ?? "",
ValidExitCode = Context.Parameters["ValidExitCode"] ?? "*"
};
try
{
commandArgs.WindowStyle = (ProcessWindowStyle)Enum.Parse(typeof(ProcessWindowStyle), Context.Parameters["WindowStyle"] ?? "Hidden");
}
catch (Exception err)
{
throw new Exception($"Invalid WindowStyle parameter value: {Context.Parameters["WindowStyle"]}", err);
}
InternalExecuteCommand(commandArgs);
}
private void InternalExecuteCommand(CommandArgs commandArgs)
{
if (string.IsNullOrEmpty(commandArgs.FileName))
throw new Exception("FileName is not specified.");
System.Diagnostics.ProcessStartInfo startInfo = new ProcessStartInfo(commandArgs.FileName, commandArgs.Args);
if (!string.IsNullOrEmpty(commandArgs.WorkingDir))
startInfo.WorkingDirectory = commandArgs.WorkingDir;
startInfo.WindowStyle = commandArgs.WindowStyle;
using (var process = Process.Start(startInfo))
{
process.WaitForExit();
if (commandArgs.ValidExitCode != "*")
{
if (process.ExitCode.ToString() != commandArgs.ValidExitCode)
throw new Exception($"Executing {commandArgs.FileName} {commandArgs.Args} returned exit code {process.ExitCode}. Expected exit code is: {commandArgs.ValidExitCode}.");
}
}
}
private static string StripDoubleSlash(string value)
{
return value.Replace("\\\\", "\\");
}
}
}

Resources