Showing posts with label Azure Devops. Show all posts
Showing posts with label Azure Devops. Show all posts

Monday, October 25, 2021

Azure Devops- Code Coverage report for typescript

 For running unit test, you can use Jasmine and the coverage report can be generated with nyc and report type as cobertura, which is supported in Azure DevOps.

Jasmine.json

{
    "spec_dir""test",
    "spec_files": ["**/*spec.ts"],
    "helpers": [
        "helpers/*.js"
    ]
 }

If you dont have any helper files, then you can remove the helpers section in above json file.

*.spec.ts file will have your test. Sample file below

describe('calculate', function() { it('add', function() { let result = Calculator.Sum(5, 2); expect(result).equal(7); }); });


Package.json

 "scripts": {
"test""ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json"
}


Now lets start the changes for code coverage.

First add an index.js file for cobertura report generation

'use strict'; /* Copyright 2012-2015, Yahoo Inc. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ const path = require('path'); const { escape } = require('html-escaper'); const { ReportBase } = require('istanbul-lib-report'); class CoberturaReport extends ReportBase { constructor(opts) { super(); opts = opts || {}; this.cw = null; this.xml = null; this.timestamp = opts.timestamp || Date.now().toString(); this.projectRoot = opts.projectRoot || process.cwd(); this.file = opts.file || 'cobertura-coverage.xml'; } onStart(root, context) { this.cw = context.writer.writeFile(this.file); this.xml = context.getXMLWriter(this.cw); this.writeRootStats(root); } onEnd() { this.xml.closeAll(); this.cw.close(); } writeRootStats(node) { const metrics = node.getCoverageSummary(); this.cw.println('<?xml version="1.0" ?>'); this.cw.println( '<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">' ); this.xml.openTag('coverage', { 'lines-valid': metrics.lines.total, 'lines-covered': metrics.lines.covered, 'line-rate': metrics.lines.pct / 100.0, 'branches-valid': metrics.branches.total, 'branches-covered': metrics.branches.covered, 'branch-rate': metrics.branches.pct / 100.0, timestamp: this.timestamp, complexity: '0', version: '0.1' }); this.xml.openTag('sources'); this.xml.inlineTag('source', null, this.projectRoot); this.xml.closeTag('sources'); this.xml.openTag('packages'); } onSummary(node) { const metrics = node.getCoverageSummary(true); if (!metrics) { return; } this.xml.openTag('package', { name: node.isRoot() ? 'main' : escape(asJavaPackage(node)), 'line-rate': metrics.lines.pct / 100.0, 'branch-rate': metrics.branches.pct / 100.0 }); this.xml.openTag('classes'); } onSummaryEnd(node) { const metrics = node.getCoverageSummary(true); if (!metrics) { return; } this.xml.closeTag('classes'); this.xml.closeTag('package'); } onDetail(node) { const fileCoverage = node.getFileCoverage(); const metrics = node.getCoverageSummary(); const branchByLine = fileCoverage.getBranchCoverageByLine(); this.xml.openTag('class', { name: escape(asClassName(node)), filename: path.relative(this.projectRoot, fileCoverage.path), 'line-rate': metrics.lines.pct / 100.0, 'branch-rate': metrics.branches.pct / 100.0 }); this.xml.openTag('methods'); const fnMap = fileCoverage.fnMap; Object.entries(fnMap).forEach(([k, { name, decl }]) => { const hits = fileCoverage.f[k]; this.xml.openTag('method', { name: escape(name), hits, signature: '()V' //fake out a no-args void return }); this.xml.openTag('lines'); //Add the function definition line and hits so that jenkins cobertura plugin records method hits this.xml.inlineTag('line', { number: decl.start.line, hits }); this.xml.closeTag('lines'); this.xml.closeTag('method'); }); this.xml.closeTag('methods'); this.xml.openTag('lines'); const lines = fileCoverage.getLineCoverage(); Object.entries(lines).forEach(([k, hits]) => { const attrs = { number: k, hits, branch: 'false' }; const branchDetail = branchByLine[k]; if (branchDetail) { attrs.branch = true; attrs['condition-coverage'] = branchDetail.coverage + '% (' + branchDetail.covered + '/' + branchDetail.total + ')'; } this.xml.inlineTag('line', attrs); }); this.xml.closeTag('lines'); this.xml.closeTag('class'); } } function asJavaPackage(node) { return node .getRelativeName() .replace(/\//g, '.') .replace(/\\/g, '.') .replace(/\.$/, ''); } function asClassName(node) { return node.getRelativeName().replace(/.*[\\/]/, ''); } module.exports = CoberturaReport;


Add coverage script and index file reference in package.json

"main""index.js",
 "scripts": {
 "test""ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
 "coverage""nyc --reporter=lcov --reporter=text-lcov -r text -e .ts -x \"test/*.spec.ts\" npm run test"
}

"files": [
    "dist/**/*",
    "index.js"
  ]

"devDependencies": {

"jasmine""3.7.0",
"nyc""^15.1.0",
"ts-node""9.1.1",
"typescript""4.2.4"
}


Now coming to Azure devops pipeline.

Add the npm task for test script

taskNpm@1
          displayName'Run npm test'
          inputs:
            command'custom'
            workingDir'$(System.DefaultWorkingDirectory)/foldername'
            customCommand'run test'
            customRegistry'useFeed'
            customFeed'd1b0b66c-98e1-4731-aab9-7ba4e6c12630'
          conditionsucceededOrFailed()

For code coverage report, run the npm task by calling coverage script added in package

taskNpm@1
          displayName'Run npm coverage'
          inputs:
            command'custom'
            workingDir'$(System.DefaultWorkingDirectory)/foldername'
            customCommand'run coverage'
            customRegistry'useFeed'
            customFeed'd1b0b66c-98e1-4731-aab9-7ba4e6c12630'
          conditionsucceededOrFailed()
taskPublishCodeCoverageResults@1
          inputs:
            codeCoverageTool'Cobertura'
            summaryFileLocation'**/*cobertura-coverage.xml'


Thats it and you can see the code coverage report in your release output

Sunday, October 17, 2021

Powershell to update Azure DevOps pipeline variables automatically during the execution

 

Sometimes we have a scenario to update the devops pipeline variables dynamically during the task execution without having to update manually and create new release. you can use the below approach by adding Powershell task to the pipeline.

# Write your PowerShell commands here.

$cosmosconnstr = "$(cosmosconnstring)"

if("$(cosmosconnstring)".Chars("$(cosmosconnstring)".Length - 1) -eq ';')

{

  $cosmosconnstr = "$(cosmosconnstring)".TrimEnd(';')

}



Write-Output("##vso[task.setvariable variable=ApplicationSettings.CacheConnection;]$cosmosconnstr")


#End


#Run the below script in another stage to make sure you get the updated value

Write-host "CacheConnection Variable in previous task is: $(ApplicationSettings.CacheConnection)"

Monday, September 20, 2021

TFS Build failure: folder cannot be deleted because it is not empty

When we run the build pipeline in Azure Devops, if we encounter below error then you can make the change recommended here.

Issue

D:\DevOps\Build\33\s\PT\<foldername>cannot be deleted because it is not empty. 

D:\DevOps\Build\33\s\PT\<foldername> cannot be deleted because it is not empty.

---- Summary: 0 conflicts, 1 warnings, 0 errors ---- 

Sleeping for 200 ms 

Retrying. Attempt $2/$3 

##[error]_proc should be null. (Parameter '_proc')


Cause

You can perform different kinds of cleaning of the working directory of your private agent before the build is run. If the Clean is set to false, it does not  get a fresh pull before the build is run.


Resolution


Change the clean dropdown to true and the error goes of in the next run.