Question

I'm having some issues getting dotCover to work in an Albacore exec task using relative paths.

@xUnitRunnerPath = Pathname.new('../../Tools/xUnit/xunitcontrib-dotcover.2.0/xunit.runner.utility.dll').realpath
@myTestDll = 'C:\PathToProj\My.Project.Tests\bin\Release\My.project.Tests.dll'
@outputDir = 'C:\PathToTestResults\'

exec :testCoverage do |cmd|
    cmd.command = "C:/BuildAgent/tools/dotCover/dotCover.exe"
    cmd.parameters = [
        "cover",
        "/targetexecutable=$$#{@xUnitRunnerPath}$$",
        "/targetarguments=$$#{@myTestDll}$$",
        "/output=#{@outputDir}/My.Project.Tests.dll.dcvr"
    ]
end

The dotCover error is unhelpful just telling me paths are wrong

Failed to convert relative paths to absolute in parameters for the 'cover' 
command. The given path's format is not supported. 

This doesn't provide much help and I've also tried dotcover help cover for the help on that but doesn't give many clues as to what's going wrong.

I've followed this post about rake and dotcover and also this question. Maybe I'm missing the relevant documentation here but it would be really helpful to be able to get this working.


EDIT: I just found this relating to relative and absolute paths, perhaps because I'm using absolute paths I need the following. We'll find out tomorrow

/AnalyseTargetArguments=false
Was it helpful?

Solution 2

For anyone that's interested here's my final rake tasks working

task :unitTestsWithCoverageReport => [ :unitTestsWithCoverage, :coverageServiceMessage ]

exec :unitTestsWithCoverage do |cmd|
    fullPathAssemblies = []

    @unitTestAssemblies.each do |testAssembly|
        testAssemblyRealPath = Pathname.new(testAssembly).realpath
        fullPathAssemblies << testAssemblyRealPath
    end

    cmd.command = @dotCoverRealPath
    cmd.parameters = [
        "cover",
        "/AnalyseTargetArguments=False",
        "/TargetExecutable=#{@xUnitRunnerRealPath}",
        "/TargetArguments=\"#{fullPathAssemblies.join ';'}\"",
        "/Output=#{@testResultsRealPath}/coverage.dcvr"
        ]
end

task :coverageServiceMessage do |t|
    puts "##teamcity[importData type='dotNetCoverage' tool='dotcover' path='#{@testResultsRealPath}/coverage.dcvr']"
end

Many thanks to @AnthonyMastrean as he showed me some really nice little ruby tricks and how I should be structuring my rake file properly.

OTHER TIPS

I'm going to remix the rakefile/tasks from your own answer. There are some Ruby/Rake conventions that you should follow to appeal to a broader audience. And I have some opinions on how to write awesome rakefiles. In particular...

1. Don't invoke/execute Rake tasks directly

Rake::Task[:unitTestWithCoverage].execute( testAssembly )

There's all sorts of reasons why you don't want to mess with direct Rake invoke or execute. One of them doesn't invoke dependent tasks, one only runs dependent tasks once... it gets goofy. There should always be a way to construct properly defined and dependent tasks.

2. Don't parameterize "internal" tasks

exec :unitTestWithCoverage, [:testAssembly] do |cmd, testAssembly|

You probably have a static list or wildcard-matching list of test assemblies. You should be able to construct concrete tasks without using parameters. I only use parameterized tasks when the user can invoke them with custom input from the command line.

3. No need to create paths inside each task

testAssemblyRealPath = Pathname.new(testAssembly).realpath
testAssemblyName = File.basename(testAssemblyRealPath)

We're gonna explore the Rake FileList to figure out how to create custom, lazy, mapped lists of filenames/paths/arbitrary-strings!

The Remix (updated)

I made a critical mistake in my first answer (which I maintain at the bottom, for reference). I'll explain what went wrong in that section for your/my education!

What follows is my new recommendation. This should have been obvious to me because I made the same mistake with an mspec test runner task in my own builds.

dotcover_path = 'path/to/dotcover.exe'
xunit_runner_path = 'path/to/xunitrunner.exe'

test_assemblies = FileList['path/to/output/**/*.test.dll']
coverage_results = "#{test_results_path}/coverage_results.dcvr"

task :cover_all => [ :tests_with_coverage, :publish_coverage_results ]

exec :tests_with_coverage do |cmd|
  cmd.comand = dotcover_path
  cmd.parameters = [ 
    "cover",
    "/AnalyseTargetArguments=False",
    "/TargetExecutable=\"#{xunit_runner_path}\"",
    "/TargetArguments=\"#{test_assemblies.join ','}\"",
    "/Output=\"#{coverage_results}\""
  ]
end

task :publish_coverage_results => [ :tests_with_coverage ] do 
  import_data 'dotNetCoverage', 'dotCover', coverage_results
end

def import_data(type, tool, file)
  puts "##teamcity[importData type='#{type}' tool='#{tool}' path='#{file}']"
end

The Explanation

I default to absolute paths (usually by using File.expand_path and the __FILE__ constant). There are tools/tasks that require relative paths, but you can always play with methods like File.basename.

dotcover_path = 'path/to/dotcover.exe'
xunit_runner_path = 'path/to/xunitrunner.exe'

We can still use a FileList of built assemblies to define the target assemblies. We won't evaluate it until the body of the test task executes.

test_assemblies = FileList['path/to/output/**/*.test.dll']

The coverage runner supports multiple assemblies with a single results file. This way we don't have another complicated pathmap.

coverage_results = "#{test_results_path}/coverage_results.dcvr"

Call this from your CI server to run the tests and the coverage results published.

task :cover_all => [ :tests_with_coverage, :publish_coverage_results ]

This task is now plain and simple. Some notes: 1. Use join to turn the list of targets into a string of the correct format. 2. I tend to quote exec task parameters that have file paths (which requires escaping, \").

exec :tests_with_coverage do |cmd|
  cmd.command = dotcover_path
  cmd.parameters = [ 
    "cover",
    "/AnalyseTargetArguments=False",
    "/TargetExecutable=\"#{xunit_runner_path}\"",
    "/TargetArguments=\"#{test_assemblies.join ','}\"",
    "/Output=\"#{coverage_results}\""
  ]
end

Same old publish task/method.

task publish_coverage_results => [ :tests_with_coverage ] do 
  import_data 'dotNetCoverage', 'dotCover', coverage_results
end

def import_data(type, tool, file)
  puts "##teamcity[importData type='#{type}' tool='#{tool}' path='#{file}']"
end

The Old Remix

Snipped to show the problem area, assume the rest was uninteresting or exists in the new solution, too.

The test assemblies won't exist until after the build task. That's normally not a problem, since FileList is lazy. It won't evaluate until you enumerate over it (for example, by using each, map, or zip).

However, we immediately each over it to generate the test tasks... so this won't work. It'll have nothing in the list and generate no tasks. Or, even worse, it'll pickup the previous build's output and possibly do bad things (if you didn't completely clean the output directory).

test_assemblies = FileList['path/to/output/**/*.test.dll']
coverage_results = test_assemblies.pathmap "#{test_results_path}/%n.dcvr"
cover_task_names = test_assemblies.pathmap "cover_%n"

test_assemblies.zip(coverage_results, cover_task_names) do |assembly, results, task_name|
  exec task_name do |cmd|
    cmd.command = dotcover_path
    cmd.parameters = [ 
      "cover",
      "/AnalyseTargetArguments=False",
      "/TargetExecutable=#{xunit_path}",
      "/TargetArguments=#{assembly}",
      "/Output=#{results}"
    ]
  end
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top