Friday, March 16, 2012

Telerik JustMock enabling the profiler from the command line

I recently ran into the issue of enabling the JustMock profiler for nUnit console on our build server, this is needed to mock statics and concrete classes. Telerik points to a solution but this is over a year old and no longer works, also all of their descriptions of how to enable this programatically also do not work. Telerik also decided to include the JustMockRunner from the mentioned blog in their newer releases in Program Files (x86)\Telerik\JustMock\Libraries\ This actually does work, so Telerik must have modified their version but there's a huge problem with this version, it doesn't allow the passing of any arguments to nunit or allow you to supply more than one test dll. This is a big problem for us so after some digging and reverse engineering I've re-written the JustMockRunner application to just pass all the arguments supplied to nUnit. Actually you don't even have to use nUnit, it should work with any command line unit tester.

Telerik documents 2 or 3 environment variables depending on where you read it that are involved in enabling the profiler, they added another one recently which causes all the old documentation and programs to fail. This new variable is JUSTMOCK_INSTANCE which needs to be set to the process ID of the running program.

I also noticed that the old JustMockRunner was capturing the output into a string and when everything finishes it all gets dumped to the screen at once. I find this behavior to be very annoying and results in a "is it actually running?" moment and with our 1,000+ unit tests there's quite a while for any feedback about what is going on. I have no idea why it was done this way as the default behavior of running another process is to display its output in the current console window. So my version removes it and makes it as simple and flexible as possible.

Without further adieu here's my code:

Update: There's some interest in this solution so I have updated to my code to a later version I came up with that preserves all arguments that are passed to it in their original form. This was a problem when trying to pass arguments with spaces in them like /loc="c:\Program Files\"
using System;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;

namespace JustMockRunner
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Requires at least 1 argument, nunit-console.exe location");
                return;
            }
            string command, arguments;
            ParseCommand(Environment.CommandLine.Trim(), out command, out arguments);
            
            var process = new Process();
            var processStartInfo = new ProcessStartInfo(command)
                                       {
                                           Arguments = arguments,
                                           UseShellExecute = false
                                       };

            processStartInfo.EnvironmentVariables["COR_ENABLE_PROFILING"] = "0x1";
            processStartInfo.EnvironmentVariables["COR_PROFILER"] = "{D1087F67-BEE8-4f53-B27A-4E01F64F3DA8}";
            processStartInfo.EnvironmentVariables["COMPLUS_ProfAPI_ProfilerCompatibilitySetting"] = "EnableV2Profiler";
            processStartInfo.EnvironmentVariables["JUSTMOCK_INSTANCE"] = Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture);


            process.StartInfo = processStartInfo;
            process.Start();
            process.WaitForExit();
        }

        private static void ParseCommand(string input, out string command, out string arguments)
        {
            var reg = new Regex(@"(^""[^""]*(?:\.[^""]*)*""|^\s*\S*)(.*)");
            var parts = reg.Match(reg.Match(input).Groups[2].Value.Trim());

            command = parts.Groups[1].Value.Trim();
            arguments = parts.Groups[2].Value.Trim();
        }
    }
}

6 comments:

  1. Awesome, you saved my build!
    Thank you for sharing

    ReplyDelete
  2. Btw, I uptaded slighly your code to:
    - allow arguments containing spaces (example: [...] /testlist:"Unit Tests")
    - catch any exceptions to not crash the build server if a parameter is wrong
    - exit with the exit code of the process

    I use it with mstest and ncover, it works like a charm. Thanks again for your post, here is the code:



    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq;

    namespace JustMockRunner
    {
    internal class JustMockRunner
    {
    private static void Main(string[] args)
    {
    Process process = new Process();
    string unitTestRunner = "";
    string unitTestRunnerArgs = "";

    try
    {
    string[] args2 = Environment.GetCommandLineArgs();
    if (args.Length == 0)
    {
    Console.WriteLine("Requires at least 1 argument, .exe location");
    return;
    }
    unitTestRunner = args[0].Replace(Environment.NewLine, string.Empty);
    unitTestRunnerArgs = "";
    foreach (string arg in args.Skip(1))
    {
    unitTestRunnerArgs += "\"" + arg.Replace(Environment.NewLine, string.Empty) + "\" ";
    }

    var processStartInfo = new ProcessStartInfo(unitTestRunner)
    {
    Arguments = unitTestRunnerArgs,
    UseShellExecute = false
    };


    processStartInfo.EnvironmentVariables["COR_ENABLE_PROFILING"] = "0x1";
    processStartInfo.EnvironmentVariables["COR_PROFILER"] = "{D1087F67-BEE8-4f53-B27A-4E01F64F3DA8}";
    processStartInfo.EnvironmentVariables["COMPLUS_ProfAPI_ProfilerCompatibilitySetting"] = "EnableV2Profiler";
    processStartInfo.EnvironmentVariables["JUSTMOCK_INSTANCE"] = Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture);

    process.StartInfo = processStartInfo;
    process.Start();
    process.WaitForExit();
    }

    catch (Exception e)
    {
    Console.WriteLine("Error in launching Unit Tests. Command line: <" + unitTestRunner + "> " + unitTestRunnerArgs + ".\nDetails: \n" + e);
    Environment.Exit(-1);
    }

    Environment.Exit(process.ExitCode);
    }
    }
    }

    ReplyDelete
    Replies
    1. I just noticed there were comments on this post, I'm not used to that happening.

      I've actually re-written this JustMockRunner program slightly since I made the post, I ran into the same problem as you with spaces in my arguments and implemented a solution in a slightly different way. I'm going to take a few minutes to compare our solutions and then I'll update my post with better code.

      Thanks!

      Delete
  3. Mathieu, How are you wrapping NCover around the runner/tests call? When I do this, NCover tries to report on the JustMockRunner.exe -- not the assemblies in the test.

    ReplyDelete
  4. Nevermind. I was calling NCover first rathe rthan JustMockRunner. Works great, Thanks!

    ReplyDelete
  5. This is great!!! Thanks so much for sharing! Telerik should take a hint from this post about how needlessly complicated and aloof their documentation seems to be!

    ReplyDelete