diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index f1e3d20..d4cc10f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ +[Dd]ist/ +bower_components/ +[tT]ypings/ # Visual Studio 2015 cache/options directory .vs/ diff --git a/TFSExtension/BuildResultsEnhancer/bower.json b/TFSExtension/BuildResultsEnhancer/bower.json new file mode 100644 index 0000000..2dc83ea --- /dev/null +++ b/TFSExtension/BuildResultsEnhancer/bower.json @@ -0,0 +1,19 @@ +{ + "name": "build-results-enhancer", + "homepage": "https://github.com/Microsoft/vso-extension-samples", + "description": "", + "main": "", + "moduleType": [], + "license": "MIT", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "vss-web-extension-sdk": "^1.95.2" + } +} \ No newline at end of file diff --git a/TFSExtension/BuildResultsEnhancer/css/main.css b/TFSExtension/BuildResultsEnhancer/css/main.css new file mode 100644 index 0000000..d9293e3 --- /dev/null +++ b/TFSExtension/BuildResultsEnhancer/css/main.css @@ -0,0 +1,26 @@ +.form-inline { + display: table; + font-size: 14px; +} + +.form-inline .form-pair { + display: table-row; +} + +.form-inline .form-key { + display: table-cell; + vertical-align: top; + padding: 6px 16px 0px 10px; + font-weight: bold +} + +.form-inline .form-value { + display: table-cell; + padding-top: 6px; +} + +.grid-caption { + margin-left: 6px; + font-family: "Segoe UI Light", "Segoe UI", sans-serif; + font-size: 14px; +} \ No newline at end of file diff --git a/TFSExtension/BuildResultsEnhancer/images/fail.jpg b/TFSExtension/BuildResultsEnhancer/images/fail.jpg new file mode 100644 index 0000000..2d1ab17 Binary files /dev/null and b/TFSExtension/BuildResultsEnhancer/images/fail.jpg differ diff --git a/TFSExtension/BuildResultsEnhancer/images/none.jpg b/TFSExtension/BuildResultsEnhancer/images/none.jpg new file mode 100644 index 0000000..07daf10 Binary files /dev/null and b/TFSExtension/BuildResultsEnhancer/images/none.jpg differ diff --git a/TFSExtension/BuildResultsEnhancer/images/running.jpg b/TFSExtension/BuildResultsEnhancer/images/running.jpg new file mode 100644 index 0000000..37e3bb9 Binary files /dev/null and b/TFSExtension/BuildResultsEnhancer/images/running.jpg differ diff --git a/TFSExtension/BuildResultsEnhancer/images/success.jpg b/TFSExtension/BuildResultsEnhancer/images/success.jpg new file mode 100644 index 0000000..dacba7b Binary files /dev/null and b/TFSExtension/BuildResultsEnhancer/images/success.jpg differ diff --git a/TFSExtension/BuildResultsEnhancer/infoTab.html b/TFSExtension/BuildResultsEnhancer/infoTab.html new file mode 100644 index 0000000..fa1f1c4 --- /dev/null +++ b/TFSExtension/BuildResultsEnhancer/infoTab.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html style="overflow:scroll"> +<head> + <script src="bower_components/vss-web-extension-sdk/lib/VSS.SDK.min.js"></script> + + <script type="text/javascript"> + VSS.init( { + usePlatformScripts: true, + moduleLoaderConfig: { + paths: { "enhancer": "BuildResultsEnhancer/dist/enhancer" } + } + }); + + VSS.ready(function() { + require(["enhancer/tab"], function () { }); + require(["enhancer/common"], function () { }); + }); + </script> + + <style> + .build-info { + padding: 10px; + } + </style> +</head> + +<body > + <div class="build-info"> + <pre id="roberts-info-container"></pre> + </div> +</body> +</html> diff --git a/TFSExtension/BuildResultsEnhancer/src/enhancer/common.ts b/TFSExtension/BuildResultsEnhancer/src/enhancer/common.ts new file mode 100644 index 0000000..b81f22e --- /dev/null +++ b/TFSExtension/BuildResultsEnhancer/src/enhancer/common.ts @@ -0,0 +1,91 @@ +/// <reference path='../../typings/main.d.ts' /> + +import TFS_BuildContracts = require("TFS/Build/Contracts"); +import TFSTestMgmtClient = require("TFS/TestManagement/RestClient"); +import TFS_TestMgmtContracts = require("TFS/TestManagement/Contracts"); + +export class OpenCoverResult extends Object { + private resdoc: XMLDocument; + public sequenceCoverage: string; + public visitedSequencePoints: string; + public sequencePoints: string; + public branchCoverage: string; + public visitedBranchPoints: string; + public branchPoints: string; + public minCyclomaticComplexity: string; + public maxCyclomaticComplexity: string; + public visitedClasses: string; + public classes: string; + public visitedMethods: string; + public methods: string; + + constructor(buffer: ArrayBuffer) { + super(); + + var cont = String.fromCharCode.apply(null, new Uint8Array(buffer)); + this.resdoc = $.parseXML(cont); + var $xml = $(this.resdoc); + this.sequenceCoverage = $xml.find("Summary").attr("sequenceCoverage"); + this.visitedSequencePoints = $xml.find("Summary").attr("visitedSequencePoints"); + this.sequencePoints = $xml.find("Summary").attr("numSequencePoints"); + + this.branchCoverage = $xml.find("Summary").attr("branchCoverage"); + this.visitedBranchPoints = $xml.find("Summary").attr("visitedBranchPoints"); + this.branchPoints = $xml.find("Summary").attr("numBranchPoints"); + + this.minCyclomaticComplexity = $xml.find("Summary").attr("minCyclomaticComplexity"); + this.maxCyclomaticComplexity = $xml.find("Summary").attr("maxCyclomaticComplexity"); + this.visitedClasses = $xml.find("Summary").attr("visitedClasses"); + this.visitedMethods = $xml.find("Summary").attr("visitedMethods"); + this.classes = $xml.find("Summary").attr("numClasses"); + this.methods = $xml.find("Summary").attr("numMethods"); + } + + public doSomethingElse(): string { + return "Hallo It's me. An Object Child"; + } + + + +} + + +export class CustomTestRunAttachment extends Object { + private tagPrefix: string = "traid_"; + private build: TFS_BuildContracts.Build; + private testrunAttachmentId: any; + + constructor(build: TFS_BuildContracts.Build) { + super(); + this.build = build; + $.each(build.tags, (ix, tag) => { + if (tag.indexOf(this.tagPrefix) == 0) { + this.testrunAttachmentId = tag.substr(6); // There should only be one testrunAttachment with an Tag pointing to it... + } + }); + } + + public getTestRunsAsync(callback: any): void { + var testrunid; + var tc = TFSTestMgmtClient.getClient(); + + + tc.getTestRuns(this.build.project.id, this.build.uri).then((tr) => { + $.each(tr, (ix, tr) => { + if (tr.name.indexOf("OpenCover_TestRun_") >= 0) { + testrunid = tr.id; + return false; // We take the first one as we only provide a single result ..... + } + }); + tc.getTestRunAttachmentContent(this.build.project.id, testrunid, this.testrunAttachmentId).then((ab) => { + callback(ab); + }); + }); + } + +} + + + + + \ No newline at end of file diff --git a/TFSExtension/BuildResultsEnhancer/src/enhancer/status.ts b/TFSExtension/BuildResultsEnhancer/src/enhancer/status.ts new file mode 100644 index 0000000..d712b3f --- /dev/null +++ b/TFSExtension/BuildResultsEnhancer/src/enhancer/status.ts @@ -0,0 +1,58 @@ +/// <reference path='../../typings/main.d.ts' /> + +import Controls = require("VSS/Controls"); +import TFS_Build_Contracts = require("TFS/Build/Contracts"); +import TFS_Build_Extension_Contracts = require("TFS/Build/ExtensionContracts"); + +// Die eigenen imports sind als ts/js im selben Verzeichnis. +import MyCommon = require("enhancer/common"); + +export class StatusSection extends Controls.BaseControl { + constructor() { + super(); + } + + public initialize(): void { + super.initialize(); + + + // Get configuration that's shared between extension and the extension host + var sharedConfig: TFS_Build_Extension_Contracts.IBuildResultsViewExtensionConfig = VSS.getConfiguration(); + + if(sharedConfig) { + // register your extension with host through callback + sharedConfig.onBuildChanged((build: TFS_Build_Contracts.Build) => { + var ctra = new MyCommon.CustomTestRunAttachment(build); + ctra.getTestRunsAsync((ab) => { + var ocr = new MyCommon.OpenCoverResult(ab); + + var sc = Number(ocr.sequenceCoverage) * 255 / 100; + var bc = Number(ocr.branchCoverage) * 255 / 100; + + + + $("#robert-cov-seq").text(ocr.sequenceCoverage + "%").css("background-color", this.rgbToHex(255 - sc, sc, 0)); + $("#robert-cov-bra").text(ocr.branchCoverage + "%").css("background-color", this.rgbToHex(255 - bc,bc, 0)); + }); + + }); + } + } + + private componentToHex(c :number) :string { + var hex = c.toString(16); + return hex.length == 1 ? "0" + hex : hex; + } + + private rgbToHex(r :number, g: number, b : number) :string { + return "#" + this.componentToHex(Math.floor(r)) + this.componentToHex(Math.floor(g)) + this.componentToHex(Math.floor(b)); + } + +} + +StatusSection.enhance(StatusSection, $(".build-status"), {}); + +// Notify the parent frame that the host has been loaded +VSS.notifyLoadSucceeded(); + + \ No newline at end of file diff --git a/TFSExtension/BuildResultsEnhancer/src/enhancer/tab.ts b/TFSExtension/BuildResultsEnhancer/src/enhancer/tab.ts new file mode 100644 index 0000000..03b94b4 --- /dev/null +++ b/TFSExtension/BuildResultsEnhancer/src/enhancer/tab.ts @@ -0,0 +1,223 @@ +/// <reference path='../../typings/main.d.ts' /> + +import Controls = require("VSS/Controls"); +import TFS_BuildContracts = require("TFS/Build/Contracts"); +import TFS_BuildContractsExt = require("TFS/Build/ExtensionContracts"); + + +import MyCommon = require("enhancer/common"); + +//import VSS_Service = require("VSS/Service"); +//import VSS_VSS = require("VSS/VSS"); +//import VSS_FileContainerServices = require("VSS/FileContainer/Services"); +//import VSS_FileContainerClient = require("VSS/FileContainer/RestClient"); +//import VSS_AtefactServices = require("VSS/Artifacts/Constants"); + + + +//import TFSBuildClient = require("TFS/Build/RestClient"); +//import TFS_CoreContracts = require("TFS/Core/Contracts"); +//import TFSCoreClient = require("TFS/Core/RestClient"); +//import TFS_DtContracts = require("TFS/DistributedTask/Contracts"); +//import TFSDtClient = require("TFS/DistributedTask/TaskRestClient"); +//import TFSDtAgentClient = require("TFS/DistributedTask/TaskAgentRestClient"); +//import TFS_VC_Contracts = require("TFS/VersionControl/Contracts"); +//import TFS_VC_Controls = require("TFS/VersionControl/Controls"); +//import TFSGitClient = require("TFS/VersionControl/GitRestClient"); +//import TFS_VC_Services = require("TFS/VersionControl/Services"); +//import TFSVcCLient = require("TFS/VersionControl/TfvcRestClient"); +//import TFS_VC_UIContracts = require("TFS/VersionControl/UIContracts"); +//import TFSWitBatchClient = require("TFS/WorkItemTracking/BatchRestClient"); +//import TFS_WIT_Contracts = require("TFS/WorkItemTracking/Contracts"); +//import TFS_WIT_ExtContracts = require("TFS/WorkItemTracking/ExtensionContracts"); +//import TFS_WIT_ProcContracts = require("TFS/WorkItemTracking/ProcessContracts"); +//import TFS_WIT_ProcDefContracts = require("TFS/WorkItemTracking/ProcessDefinitionsContracts"); +//import TFSWitProcDefClient = require("TFS/WorkItemTracking/ProcessDefinitionsRestClient"); +//import TFSWitProcClient = require("TFS/WorkItemTracking/ProcessRestClient"); +//import TFS_WIT_ProcTemplContracts = require("TFS/WorkItemTracking/ProcessTemplateContracts"); +//import TFSWitProcTemplClient = require("TFS/WorkItemTracking/ProcessTemplateRestClient"); +//import TFSWitClient = require("TFS/WorkItemTracking/RestClient"); +//import TFS_WIT_Services = require("TFS/WorkItemTracking/Services"); +//import TFS_WIT_UIContracts = require("TFS/WorkItemTracking/UIContracts"); +//import TFS_Work_Contracts = require("TFS/Work/Contracts"); +//import TFSWorkClient = require("TFS/Work/RestClient"); + +//import VFS_UtilsFile = require("VSS/Utils/File"); +//import VFS_RichEditor = require("VSS/Controls/RichEditor"); +/* other modules + "VSS/Accounts/Contracts" { + "VSS/Accounts/RestClient" { + "VSS/Adapters/Knockout" { + "VSS/Ajax" { + "VSS/Artifacts/Constants" { + "VSS/Artifacts/Services" { + "VSS/Authentication/Contracts" { + "VSS/Authentication/RestClient" { + "VSS/Authentication/Services" { + "VSS/Bundling" { + "VSS/Commerce/Contracts" { + "VSS/Commerce/RestClient" { + "VSS/Common/Constants/Platform" { + "VSS/Common/Contracts/FormInput" { + "VSS/Common/Contracts/Platform" { + "VSS/Common/Contracts/System" { + "VSS/Common/Contracts/System.Data" { + "VSS/Compatibility" { + "VSS/Context" { + "VSS/Contributions/Contracts" { + "VSS/Contributions/Controls" { + "VSS/Contributions/RestClient" { + "VSS/Contributions/Services" { + "VSS/Controls" { + "VSS/Controls/AjaxPanel" { + "VSS/Controls/CheckboxList" { + "VSS/Controls/Combos" { + "VSS/Controls/Dialogs" { + "VSS/Controls/EditableGrid" { + "VSS/Controls/ExternalHub" { + "VSS/Controls/FileInput" { + "VSS/Controls/Filters" { + "VSS/Controls/FormInput" { + "VSS/Controls/Grids" { + "VSS/Controls/Histogram" { + "VSS/Controls/Hubs" { + "VSS/Controls/KeyboardShortcuts" { + "VSS/Controls/Menus" { + "VSS/Controls/Navigation" { + "VSS/Controls/Notifications" { + "VSS/Controls/Panels" { + "VSS/Controls/PerfBar" { + "VSS/Controls/PopupContent" { + "VSS/Controls/RichEditor" { + "VSS/Controls/Search" { + "VSS/Controls/Splitter" { + "VSS/Controls/StatusIndicator" { + "VSS/Controls/TabContent" { + "VSS/Controls/TreeView" { + "VSS/Controls/Validation" { + "VSS/Controls/Virtualization" { + "VSS/DelegatedAuthorization/Contracts" { + "VSS/DelegatedAuthorization/RestClient" { + "VSS/Diag" { + "VSS/Diag/Services" { + "VSS/Error" { + "VSS/Events/Action" { + "VSS/Events/Document" { + "VSS/Events/Handlers" { + "VSS/Events/Services" { + "VSS/ExtensionManagement/Contracts" { + "VSS/ExtensionManagement/RestClient" { + "VSS/FeatureAvailability/Contracts" { + "VSS/FeatureAvailability/RestClient" { + "VSS/FeatureAvailability/Services" { + "VSS/FileContainer/Contracts" { + "VSS/FileContainer/RestClient" { + "VSS/FileContainer/Services" { + "VSS/Gallery/Contracts" { + "VSS/Gallery/RestClient" { + "VSS/Identities/Contracts" { + "VSS/Identities/Mru/Contracts" { + "VSS/Identities/Mru/RestClient" { + "VSS/Identities/Picker/Cache" { + "VSS/Identities/Picker/Constants" { + "VSS/Identities/Picker/Controls" { + "VSS/Identities/Picker/RestClient" { + "VSS/Identities/Picker/Services" { + "VSS/Identities/RestClient" { + "VSS/Licensing/Contracts" { + "VSS/Locations" { + "VSS/Locations/Contracts" { + "VSS/Locations/RestClient" { + "VSS/Navigation/Services" { + "VSS/Operations/Contracts" { + "VSS/Operations/RestClient" { + "VSS/Organization/CollectionRestClient" { + "VSS/Organization/Contracts" { + "VSS/Organization/OrganizationRestClient" { + "VSS/Performance" { + "VSS/Profile/Contracts" { + "VSS/Profile/Metrics" { + "VSS/Profile/RestClient" { + "VSS/SDK/Services/Dialogs" { + "VSS/SDK/Services/ExtensionData" { + "VSS/SDK/Services/Navigation" { + "VSS/Search" { + "VSS/SecurityRoles/Contracts" { + "VSS/SecurityRoles/RestClient" { + "VSS/Security/Contracts" { + "VSS/Security/RestClient" { + "VSS/Serialization" { + "VSS/Service" { + "VSS/Settings" { + "VSS/Telemetry/Contracts" { + "VSS/Telemetry/RestClient" { + "VSS/Telemetry/Services" { + "VSS/Utils/Array" { + "VSS/Utils/Clipboard" { + "VSS/Utils/Core" { + "VSS/Utils/Culture" { + "VSS/Utils/Date" { + "VSS/Utils/File" { + "VSS/Utils/Html" { + "VSS/Utils/Number" { + "VSS/Utils/String" { + "VSS/Utils/UI" { + "VSS/Utils/Url" { + "VSS/VSS" { + "VSS/WebApi/Constants" { + "VSS/WebApi/Contracts" { + "VSS/WebApi/RestClient" { +*/ + +export class InfoTab extends Controls.BaseControl { + constructor() { + super(); + } + + public initialize(): void { + super.initialize(); + + // Get configuration that's shared between extension and the extension host + var sharedConfig: TFS_BuildContractsExt.IBuildResultsViewExtensionConfig = VSS.getConfiguration(); + + if(sharedConfig) { + // register your extension with host through callback + sharedConfig.onBuildChanged((build: TFS_BuildContracts.Build) => { + this._initBuildInfo(build); + + var ctra = new MyCommon.CustomTestRunAttachment(build); + ctra.getTestRunsAsync((ab) => { + var openCoverResult = new MyCommon.OpenCoverResult(ab); + + var inf: string = ""; + inf += "Sequence Coverage: " + openCoverResult.sequenceCoverage + "%" + + " (" + openCoverResult.visitedSequencePoints + + "/" + openCoverResult.sequencePoints + ")\n"; + inf += "Branch Coverage: " + openCoverResult.branchCoverage + "%" + + " (" + openCoverResult.visitedBranchPoints + + "/" + openCoverResult.branchPoints + ")\n\n" + + inf += "Min Cyclomatic Complexity: " + openCoverResult.minCyclomaticComplexity + "\n"; + inf += "Max Cyclomatic Complexity: " + openCoverResult.maxCyclomaticComplexity + "\n"; + inf += "Classes visited/monitored: " + openCoverResult.visitedClasses + + "/" + openCoverResult.classes + "\n"; + inf += "Methods visited/monitored: " + openCoverResult.visitedMethods + + "/" + openCoverResult.methods + "\n"; + + $('#roberts-info-container').text(inf); + }); + }); + } + } + + private _initBuildInfo(build: TFS_BuildContracts.Build) { + + } +} + +InfoTab.enhance(InfoTab, $(".build-info"), {}); + +// Notify the parent frame that the host has been loaded +VSS.notifyLoadSucceeded(); + + \ No newline at end of file diff --git a/TFSExtension/BuildResultsEnhancer/statusSection.html b/TFSExtension/BuildResultsEnhancer/statusSection.html new file mode 100644 index 0000000..d3031c3 --- /dev/null +++ b/TFSExtension/BuildResultsEnhancer/statusSection.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<head> + <script src="bower_components/vss-web-extension-sdk/lib/VSS.SDK.min.js"></script> + + <script type="text/javascript"> + VSS.init( { + usePlatformScripts: true, + moduleLoaderConfig: { + paths: { "enhancer": "BuildResultsEnhancer/dist/enhancer" } + } + }); + + VSS.ready(function() { + require(["enhancer/status"], function () { }); + require(["enhancer/common"], function () { }); + }); + </script> + + <style> + #robert-status-root td { + padding: 0.1em + } + #robert-cov-seq, #robert-cov-bra { + font-weight: bold; + text-align: right; + } + + </style> +</head> + +<body> + <div class="build-status"> + <table> + <tr><td>Sequence Coverage:</td><td id="robert-cov-seq"></td></tr> + <tr><td>Branch Coverage:</td><td id="robert-cov-bra"></td></tr> + </table> + </div> +</body> +</html> diff --git a/TFSExtension/BuildResultsEnhancer/tsconfig.json b/TFSExtension/BuildResultsEnhancer/tsconfig.json new file mode 100644 index 0000000..de17b6a --- /dev/null +++ b/TFSExtension/BuildResultsEnhancer/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "amd", + "target": "es5", + "outDir": "dist/", + "rootDir": "src/" + }, + "files": [ + "src/enhancer/common.ts", + "src/enhancer/status.ts", + "src/enhancer/tab.ts" + ] +} \ No newline at end of file diff --git a/TFSExtension/BuildResultsEnhancer/typings.json b/TFSExtension/BuildResultsEnhancer/typings.json new file mode 100644 index 0000000..d37d250 --- /dev/null +++ b/TFSExtension/BuildResultsEnhancer/typings.json @@ -0,0 +1,9 @@ +{ + "ambientDependencies": { + "Q": "github:DefinitelyTyped/DefinitelyTyped/q/Q.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c", + "jquery": "github:DefinitelyTyped/DefinitelyTyped/jquery/jquery.d.ts#470954c4f427e0805a2d633636a7c6aa7170def8", + "knockout": "github:DefinitelyTyped/DefinitelyTyped/knockout/knockout.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c", + "tfs": "github:microsoft/vss-web-extension-sdk/typings/tfs.d.ts", + "vss": "github:microsoft/vss-web-extension-sdk/typings/vss.d.ts" + } +} diff --git a/TFSExtension/RunNunitWithOpenCover/ResultDummy.xml b/TFSExtension/RunNunitWithOpenCover/ResultDummy.xml new file mode 100644 index 0000000..fc10668 --- /dev/null +++ b/TFSExtension/RunNunitWithOpenCover/ResultDummy.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> + +<!--This file represents the results of running a test suite--> +<test-results +name="c:\this\is\a\simulated\testresul\dummy.NUnit.dll" +total="1" errors="0" failures="0" not-run="0" inconclusive="0" ignored="0" skipped="0" invalid="0" date="2001-01-01" +time="01:01:01"> + <environment nunit-version="2.5.10.11092" clr-version="2.0.50727.5448" + os-version="Microsoft Windows NT 6.1.7601 Service Pack 1" platform="Win32NT" cwd="c:\was\ist\das" + machine-name="ANYONES" user="theSimlulant" user-domain="WOAUCHIMMER" /> + <culture-info current-culture="en-US" current-uiculture="en-US" /> + <test-suite type="Assembly" + name="c:\the\assembly\name\in\testsuite-tag\dummy.NUnit.dll" + executed="True" result="Success" success="True" time="1.000" asserts="0"> + <results> + <test-case name="A.Dummy.TestCase" description="Used to create a TEstResult with Nunit2 XML" + executed="True" result="Success" success="True" time="0.001" asserts="0"> + <categories> + <category name="OpenCover Container" /> + </categories> + </test-case> + </results> + </test-suite> +</test-results> \ No newline at end of file diff --git a/TFSExtension/RunNunitWithOpenCover/RunOpenCover.ps1 b/TFSExtension/RunNunitWithOpenCover/RunOpenCover.ps1 new file mode 100644 index 0000000..c3deae2 --- /dev/null +++ b/TFSExtension/RunNunitWithOpenCover/RunOpenCover.ps1 @@ -0,0 +1,104 @@ +[cmdletbinding()] +param ( + [string]$testassembly, + [string]$registerOption, + [string]$generateReport, + [string]$coverageFilter, + [string]$tfsUser, + [string]$tfsPwd +) + +$ocToolPath = "dist\OpenCover.4.6.519\tools" +$nuToolPath = "dist\NUnit.ConsoleRunner.3.2.1\tools" +$rgToolPath = "dist\ReportGenerator.2.4.5.0\tools" +$restHelperPath = "dist\RestHelper" + +Write-Host "Entering script RunOpenCover.ps1 with Current User: '$(whoami)'" +Write-Host "testassembly = $testassembly" +Write-Host "registerOption = $registerOption" +Write-Host "generateReport = $generateReport" +Write-Host "coverageFilter = $coverageFilter" + +#Write-Host "Environment: " +#get-ChildItem env: | Foreach-object {Write-Host $_.Name = $_.Value} +#Write-Host "Context: " +#$distributedTaskContext.psobject.properties | where {$_.name -like '*'} | Foreach-object {Write-Host $_.Name = $_.Value} + + +$outputPath = $env:COMMON_TESTRESULTSDIRECTORY +if(!(Test-Path -Path $outputPath )){ + New-Item -ItemType directory -Path $outputPath +} + +Write-Host "Generate project file for Nunit Call from parameter '$testassembly'" +if ($testassembly.Contains("*") -Or $testassembly.Contains("?")) +{ + Write-Host "Calling Find-Files with pattern: $testassembly;-:**\packages\**" + [string[]] $testAssemblyFiles = @(Find-Files -SearchPattern "$testassembly;-:**\packages\**" -RootFolder $env:BUILD_SOURCESDIRECTORY) +} +else +{ + Write-Host "No Pattern found in solution parameter." + [string[]] $testAssemblyFiles = ,"$env:BUILD_SOURCESDIRECTORY\$testassembly" +} + +$nunitprojectroot = "" +$nunitproject = "<NUnitProject> + <Settings activeconfig=""Debug""/> + <Config name=""Debug"">" +foreach ($testfile in $testAssemblyFiles) { + if (!$nunitprojectroot) { + $nunitprojectroot = split-path $testfile + } + $nunitproject = $nunitproject + "<assembly path=""$testfile""/>" +} +$nunitproject = $nunitproject + " + </Config> +</NUnitProject>" + +Write-Host "Generating nunit project file at $nunitprojectroot with content: $nunitproject" +$nunitproject | Out-File $nunitprojectroot\project.nunit + +$cmd = ".\${ocToolPath}\OpenCover.Console.exe" +$arg1 = "-target:"".\$nuToolPath\nunit3-console.exe""" +$arg2 = "-targetargs:""$nunitprojectroot\project.nunit --work=$outputPath""" +$arg3 = "-filter:""$coverageFilter""" +$arg4 = "-register:$registerOption" +$arg5 = "-coverbytest:*" +$arg6 = "-output:""$outputPath\CodeCoverageResult.xml""" + +Write-Host "Executing:'& $cmd $arg1 $arg2 $arg3 $arg4 $arg5 $arg6' " +& $cmd $arg1 $arg2 $arg3 $arg4 $arg5 $arg6 + +if ($generateReport -eq "true") { + $cmd = ".\${rgToolPath}\ReportGenerator.exe" + $arg1 = "-reports:""$outputPath\CodeCoverageResult.xml""" + $arg2 = "-targetdir:""$outputPath\CoverageReport""" + + Write-Host "Executing:'& $cmd $arg1 $arg2'" + & $cmd $arg1 $arg2 +} + +Publish-BuildArtifact "OpenCoverResult" $outputPath + +$exampleResult = Resolve-Path ".\dist\ResultDummy.xml" +$RunTitle = "OpenCover_TestRun_$env:BUILD_BUILDNUMBER" +Publish-TestResults -TestRunner "NUnit" -TestResultsFiles $exampleResult -MergeResults $true -Context $distributedTaskContext -PublishRunLevelAttachments $true -RunTitle $RunTitle + +$cmd = ".\${restHelperPath}\TFSRestTool.exe" +$arg1 = "$env:SYSTEM_TEAMFOUNDATIONSERVERURI$env:SYSTEM_TEAMPROJECTID/" +$arg2 = "$env:BUILD_BUILDNUMBER" +$arg3 = "$RunTitle" +$arg4 = "$tfsUser" +$arg5 = "$tfsPwd" +$arg6 = "$outputPath\CodeCoverageResult.xml" + +Write-Host "Executing:'& $cmd $arg1 $arg2 $arg3 $arg4 $arg5 $arg6' " +& $cmd $arg1 $arg2 $arg3 $arg4 $arg5 $arg6 + +Write-Host "RunOpenCover.ps1 finished." + + + + + diff --git a/TFSExtension/RunNunitWithOpenCover/icon.png b/TFSExtension/RunNunitWithOpenCover/icon.png new file mode 100644 index 0000000..7d9c8c5 Binary files /dev/null and b/TFSExtension/RunNunitWithOpenCover/icon.png differ diff --git a/TFSExtension/RunNunitWithOpenCover/task.json b/TFSExtension/RunNunitWithOpenCover/task.json new file mode 100644 index 0000000..0eab864 --- /dev/null +++ b/TFSExtension/RunNunitWithOpenCover/task.json @@ -0,0 +1,100 @@ +{ + "id": "9F56F6BE-1D7E-4940-86A8-DB552C7ABF50", + "name": "RunNunitWithOpenCover", + "friendlyName": "Runs Nunit Test with OpenCover", + "description": "Runs Nunit tests using OpenCover command line and nunit3 consolen runner to generate coverage report.", + "helpMarkDown": "", + "category": "Test", + "visibility": [ + "Build", + "Release" + ], + "author": "RKOS", + "version": { + "Major": 0, + "Minor": 0, + "Patch": 2 + }, + "demands": [ + "Cmd" + ], + "minimumAgentVersion": "1.83.0", + "groups": [ + { + "name": "output", + "displayName": "Output Options", + "isExpanded": true + }, + { + "name": "credentials", + "displayName": "TFS Credentials", + "isExpanded": true + } + ], + "inputs": [ + { + "name": "testassembly", + "type": "string", + "label": "Testassembly", + "defaultValue": "**\\*test*.dll;-:**\\obj\\**", + "required": true, + "helpMarkDown": "specifieys dlls to be run by nunit console runnner." + }, + { + "name": "registerOption", + "type": "pickList", + "label": "Register Option", + "defaultValue": "Path32", + "required": true, + "helpMarkDown": "Specify the -register Option for OpenCover call.", + "options": { + "Path32": "'Path32' uses local 32bit version of opencover service dll without registry.", + "Path64": "'Path64'uses local 64bit version of opencover service dll without registry.", + "User": "'User' registers the service dll(s) for running User Acount (BuildAgent User)." + } + }, + { + "name": "coverageFilter", + "type": "string", + "label": "Coverage Filter", + "defaultValue": "+[MyNamespace]*", + "required": true, + "helpMarkDown": "Specifies the Domain/Classes to be analyzed with Coverage Data (e.g.:'+[MyNamespace]*')." + }, + { + "name": "generateReport", + "type": "boolean", + "label": "Generate Report", + "defaultValue": "true", + "required": true, + "helpMarkDown": "If this is true, the Coverage result is used to generate HTML Report as Build Artefact.", + "groupName": "output" + }, + { + "name": "tfsUser", + "type": "string", + "label": "TFS User", + "defaultValue": "", + "required": true, + "helpMarkDown": "An User Account with access rights to TFS REST API is needed.", + "groupName": "credentials" + }, + { + "name": "tfsPwd", + "type": "string", + "label": "TFS USers Password", + "defaultValue": "$(TfsSecretPassword)", + "required": true, + "helpMarkDown": "The Passsword for the TFS User. Its better to keep default value here and enter the 'TfsSecretPassword' as hidden variable in the Build Variables Section.", + "groupName": "credentials" + } + ], + "instanceNameFormat": "RunOpenCover with script $(filename)", + "execution": { + "PowerShell": { + "target": "$(currentDirectory)\\RunOpenCover.ps1", + "argumentFormat": "", + "workingDirectory": "$(currentDirectory)" + } + } +} diff --git a/TFSExtension/TFSExtension.sln b/TFSExtension/TFSExtension.sln new file mode 100644 index 0000000..67cb8fb --- /dev/null +++ b/TFSExtension/TFSExtension.sln @@ -0,0 +1,76 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TFSRestTool", "TFSRestTool\TFSRestTool.csproj", "{783DCCBC-8A8B-4A47-9CB6-6317DC1676CD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6AB4E37D-4913-4E35-975F-847E29340BC4}" + ProjectSection(SolutionItems) = preProject + build.bat = build.bat + clean.bat = clean.bat + extension-icon128.png = extension-icon128.png + vss-extension.json = vss-extension.json + xcopy1.excl = xcopy1.excl + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RunNunitWithOpenCover", "RunNunitWithOpenCover", "{1BC2C287-01A6-41D4-88D9-8D9049047ADC}" + ProjectSection(SolutionItems) = preProject + RunNunitWithOpenCover\icon.png = RunNunitWithOpenCover\icon.png + RunNunitWithOpenCover\ResultDummy.xml = RunNunitWithOpenCover\ResultDummy.xml + RunNunitWithOpenCover\RunOpenCover.ps1 = RunNunitWithOpenCover\RunOpenCover.ps1 + RunNunitWithOpenCover\task.json = RunNunitWithOpenCover\task.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildResultsEnhancer", "BuildResultsEnhancer", "{943116C1-A8A5-4D3F-AE0E-4DC515D0C46D}" + ProjectSection(SolutionItems) = preProject + BuildResultsEnhancer\bower.json = BuildResultsEnhancer\bower.json + BuildResultsEnhancer\infoTab.html = BuildResultsEnhancer\infoTab.html + BuildResultsEnhancer\statusSection.html = BuildResultsEnhancer\statusSection.html + BuildResultsEnhancer\tsconfig.json = BuildResultsEnhancer\tsconfig.json + BuildResultsEnhancer\typings.json = BuildResultsEnhancer\typings.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EA4C2B05-4B0E-4910-A875-3FD28CB278FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "enhancer", "enhancer", "{0B205C87-842D-4F23-A862-4B44FB4B2B7D}" + ProjectSection(SolutionItems) = preProject + BuildResultsEnhancer\src\enhancer\common.ts = BuildResultsEnhancer\src\enhancer\common.ts + BuildResultsEnhancer\src\enhancer\status.ts = BuildResultsEnhancer\src\enhancer\status.ts + BuildResultsEnhancer\src\enhancer\tab.ts = BuildResultsEnhancer\src\enhancer\tab.ts + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{C21291EA-49EE-4A4B-AB88-748A02A0A419}" + ProjectSection(SolutionItems) = preProject + BuildResultsEnhancer\images\fail.jpg = BuildResultsEnhancer\images\fail.jpg + BuildResultsEnhancer\images\none.jpg = BuildResultsEnhancer\images\none.jpg + BuildResultsEnhancer\images\running.jpg = BuildResultsEnhancer\images\running.jpg + BuildResultsEnhancer\images\success.jpg = BuildResultsEnhancer\images\success.jpg + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "css", "css", "{06A82807-2A53-4006-9158-2DF67C76AC66}" + ProjectSection(SolutionItems) = preProject + BuildResultsEnhancer\css\main.css = BuildResultsEnhancer\css\main.css + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {783DCCBC-8A8B-4A47-9CB6-6317DC1676CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {783DCCBC-8A8B-4A47-9CB6-6317DC1676CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {783DCCBC-8A8B-4A47-9CB6-6317DC1676CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {783DCCBC-8A8B-4A47-9CB6-6317DC1676CD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {EA4C2B05-4B0E-4910-A875-3FD28CB278FF} = {943116C1-A8A5-4D3F-AE0E-4DC515D0C46D} + {0B205C87-842D-4F23-A862-4B44FB4B2B7D} = {EA4C2B05-4B0E-4910-A875-3FD28CB278FF} + {C21291EA-49EE-4A4B-AB88-748A02A0A419} = {943116C1-A8A5-4D3F-AE0E-4DC515D0C46D} + {06A82807-2A53-4006-9158-2DF67C76AC66} = {943116C1-A8A5-4D3F-AE0E-4DC515D0C46D} + EndGlobalSection +EndGlobal diff --git a/TFSExtension/TFSRestTool/App.config b/TFSExtension/TFSRestTool/App.config new file mode 100644 index 0000000..731f6de --- /dev/null +++ b/TFSExtension/TFSRestTool/App.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> + </startup> +</configuration> \ No newline at end of file diff --git a/TFSExtension/TFSRestTool/Program.cs b/TFSExtension/TFSRestTool/Program.cs new file mode 100644 index 0000000..611e9dd --- /dev/null +++ b/TFSExtension/TFSRestTool/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + + +namespace TFSRestTool { + class Program { + static void Main(string[] args) { + if(args.Count() > 0) { + var trh = new TfsRestHelper(args); + trh.PublishResultAttachment(); + } else { + Console.WriteLine("This Console Project exists to trigger solution build using VisualStudio."); + Console.WriteLine("The build of the TFS Extension vsxi file is triggered by calling build.bat in soultion root."); + Console.WriteLine("Toolchain 'node.js' - 'bower' - 'typings' - 'typescript'(tsc) must be installed and be globally available."); + Console.WriteLine("When successfull the result .vsxi can be found in <Solution>/bin. "); + Console.ReadLine(); + } + } + } +} diff --git a/TFSExtension/TFSRestTool/Properties/AssemblyInfo.cs b/TFSExtension/TFSRestTool/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..601e9f8 --- /dev/null +++ b/TFSExtension/TFSRestTool/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TFSRestTool")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TFSRestTool")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("783dccbc-8a8b-4a47-9cb6-6317dc1676cd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TFSExtension/TFSRestTool/TFSRestTool.csproj b/TFSExtension/TFSRestTool/TFSRestTool.csproj new file mode 100644 index 0000000..629cc67 --- /dev/null +++ b/TFSExtension/TFSRestTool/TFSRestTool.csproj @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{783DCCBC-8A8B-4A47-9CB6-6317DC1676CD}</ProjectGuid> + <OutputType>Exe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>TFSRestTool</RootNamespace> + <AssemblyName>TFSRestTool</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="TfsRestHelper.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + <None Include="packages.config" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/TFSExtension/TFSRestTool/TfsRestHelper.cs b/TFSExtension/TFSRestTool/TfsRestHelper.cs new file mode 100644 index 0000000..af8817d --- /dev/null +++ b/TFSExtension/TFSRestTool/TfsRestHelper.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using RestSharp; + +namespace TFSRestTool { + internal class TfsRestHelper { + private string[] args; + private string baseUri; + private string buildNumber; + private string testRunName; + private string agentUser; + private string agentPwd; + private string resultPath; + private const string apiVersion = "api-version=2.2-preview"; + + + public TfsRestHelper(string[] args) { + this.args = args; + if(args.Count() >= 6) { + baseUri = args[0]; + buildNumber = args[1]; + testRunName = args[2]; + agentUser = args[3]; + agentPwd = args[4]; + resultPath = args[5]; + } + } + + internal void PublishResultAttachment() { + RestClient client = new RestClient(baseUri); + // First we have to query the correct testRun by name + RestRequest request = new RestRequest("_apis/test/runs/query?$top=20&" + apiVersion, Method.POST); + request.Credentials = new NetworkCredential(agentUser, agentPwd); + request.RequestFormat = RestSharp.DataFormat.Json; + request.AddBody(new { query = $"Select * From TestRun where Title='{testRunName}'" }); + + string testRunId = String.Empty; + IRestResponse<Dictionary<string, object>> response = client.Execute<Dictionary<string, object>>(request); + if(response.StatusCode == HttpStatusCode.OK) { + if(response.Data.Count > 0) { + var runs = (JsonArray)response.Data["value"]; + var firstRun = (Dictionary<string,object>)runs[0]; + testRunId = firstRun["id"].ToString(); + } + } + + if(!String.IsNullOrEmpty(testRunId)) { + // Then We Upload the Result file as Testrun Attachment + String resultStream = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("kein result file gefunden....")); + try { + Byte[] bytes = File.ReadAllBytes(resultPath); + resultStream = Convert.ToBase64String(bytes); + } catch(Exception) { } + + // client = new RestClient(baseUri); + request = new RestRequest($"_apis/test/runs/{testRunId}/attachments/?" + apiVersion, Method.POST); + request.Credentials = new NetworkCredential(agentUser, agentPwd); + request.RequestFormat = RestSharp.DataFormat.Json; + //request.AddParameter("Application/Json", attachmentJsonText, ParameterType.RequestBody); + request.AddBody(new { + stream = resultStream, + fileName = "TheResult.Xml", + comment = "OpenCover upload", + attachmentType = "GeneralAttachment" + }); + + string attachmentId = String.Empty; + response = client.Execute<Dictionary<string, object>>(request); + if(response.StatusCode == HttpStatusCode.OK) { + if(response.Data.Count > 0) { + attachmentId = response.Data["id"].ToString(); + } + } + + if (!String.IsNullOrEmpty(attachmentId)) { + string myTag = $"traid_{attachmentId}"; + + // Finally we tag the build to have a clue to get the Attachment in Web UI Scripts.... + request = new RestRequest($"_apis/build/builds/{buildNumber}/tags/{myTag}?" + apiVersion, Method.PUT); + request.Credentials = new NetworkCredential(agentUser, agentPwd); + response = client.Execute<Dictionary<string, object>>(request); + if(response.StatusCode == HttpStatusCode.OK) { + + } + + } + } + + } + + + } +} \ No newline at end of file diff --git a/TFSExtension/TFSRestTool/packages.config b/TFSExtension/TFSRestTool/packages.config new file mode 100644 index 0000000..d5bd527 --- /dev/null +++ b/TFSExtension/TFSRestTool/packages.config @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="NUnit.ConsoleRunner" version="3.2.1" targetFramework="net461" /> + <package id="NUnit.Extension.NUnitProjectLoader" version="3.2.1" targetFramework="net461" /> + <package id="NUnit.Extension.NUnitV2Driver" version="3.2.1" targetFramework="net461" /> + <package id="NUnit.Extension.NUnitV2ResultWriter" version="3.2.1" targetFramework="net461" /> + <package id="NUnit.Extension.VSProjectLoader" version="3.2.1" targetFramework="net461" /> + <package id="NUnit.Runners" version="3.2.1" targetFramework="net461" /> + <package id="OpenCover" version="4.6.519" targetFramework="net461" /> + <package id="ReportGenerator" version="2.4.5.0" targetFramework="net461" /> + <package id="RestSharp" version="105.2.3" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/TFSExtension/build.bat b/TFSExtension/build.bat new file mode 100644 index 0000000..22e1e42 --- /dev/null +++ b/TFSExtension/build.bat @@ -0,0 +1,16 @@ +echo Build.Bat called. All extensions are build with typescript and tfx tools.... +cd BuildResultsEnhancer +echo Building BuildResultsEnhancer +call bower install +call typings install +call tsc +cd .. +echo Building RunOpenCoverTask +xcopy /S /I .\packages\NUnit.ConsoleRunner.3.2.1 .\RunNunitWithOpenCover\dist\NUnit.ConsoleRunner.3.2.1 +xcopy /S /I .\packages\NUnit.Extension.NUnitProjectLoader.3.2.1 .\RunNunitWithOpenCover\dist\NUnit.Extension.NUnitProjectLoader.3.2.1 +xcopy /S /I .\packages\OpenCover.4.6.519 .\RunNunitWithOpenCover\dist\OpenCover.4.6.519 +xcopy /S /I /Exclude:xcopy1.excl .\packages\ReportGenerator.2.4.5.0 .\RunNunitWithOpenCover\dist\ReportGenerator.2.4.5.0 +xcopy .\RunNunitWithOpenCover\ResultDummy.xml .\RunNunitWithOpenCover\dist\ +xcopy /S /I .\TFSRestTool\bin\Debug .\RunNunitWithOpenCover\dist\RestHelper + +tfx extension create --manifest-globs vss-extension.json --output-path ./bin diff --git a/TFSExtension/clean.bat b/TFSExtension/clean.bat new file mode 100644 index 0000000..f463e70 --- /dev/null +++ b/TFSExtension/clean.bat @@ -0,0 +1,6 @@ +rd /S /Q bin +rd /S /Q RunNunitWithOpenCover\dist +rd /S /Q BuildResultsEnhancer\dist +rd /S /Q BuildResultsEnhancer\typings +rd /S /Q BuildResultsEnhancer\bower_components + diff --git a/TFSExtension/extension-icon128.png b/TFSExtension/extension-icon128.png new file mode 100644 index 0000000..eceb459 Binary files /dev/null and b/TFSExtension/extension-icon128.png differ diff --git a/TFSExtension/vss-extension.json b/TFSExtension/vss-extension.json new file mode 100644 index 0000000..f47a6a9 --- /dev/null +++ b/TFSExtension/vss-extension.json @@ -0,0 +1,94 @@ +{ + "manifestVersion": 1, + "id": "coverage-build-tasks", + "name": "RKOS Build Tools", + "version": "0.0.1", + "publisher": "RKOS", + "targets": [ + { + "id": "Microsoft.VisualStudio.Services" + } + ], + "description": "Tools for running and publishing OpenCover tests.", + "categories": [ + "Build and release" + ], + "scopes": [ "vso.build", "vso.test" ], + "icons": { + "default": "extension-icon128.png" + }, + "files": [ + { + "path": "RunNunitWithOpenCover/dist" + }, + { + "path": "RunNunitWithOpenCover/task.json" + }, + { + "path": "RunNunitWithOpenCover/icon.png" + }, + { + "path": "RunNunitWithOpenCover/RunOpenCover.ps1" + }, + + { + "path": "BuildResultsEnhancer/images", + "addressable": true + }, + { + "path": "BuildResultsEnhancer/dist", + "addressable": true + }, + { + "path": "BuildResultsEnhancer/infoTab.html", + "addressable": true + }, + { + "path": "BuildResultsEnhancer/statusSection.html", + "addressable": true + }, + { + "path": "BuildResultsEnhancer/bower_components/vss-web-extension-sdk/lib/VSS.SDK.min.js", + "addressable": true + } + ], + "contributions": [ + { + "id": "rkos-run-nunit-opencover-task", + "type": "ms.vss-distributed-task.task", + "targets": [ + "ms.vss-distributed-task.tasks" + ], + "properties": { + "name": "RunNunitWithOpenCover" + } + }, + { + "id": "build-info-tab", + "type": "ms.vss-build-web.build-results-tab", + "description": "A tab contributing to build results view", + "targets": [ + "ms.vss-build-web.build-results-view" + ], + "properties": { + "name": "OpenCover", + "uri": "BuildResultsEnhancer/infoTab.html", + "order": 1 + } + }, + { + "id": "build-status-section", + "type": "ms.vss-build-web.build-results-section", + "description": "A section contributing to our own new tab and also to existing build 'summary' tab", + "targets": [ + "ms.vss-build-web.build-results-summary-tab" + ], + "properties": { + "name": "OpenCoverage Summary", + "uri": "BuildResultsEnhancer/statusSection.html", + "order": 10, + "height": 40 + } + } + ] +} \ No newline at end of file diff --git a/TFSExtension/xcopy1.excl b/TFSExtension/xcopy1.excl new file mode 100644 index 0000000..a96f929 --- /dev/null +++ b/TFSExtension/xcopy1.excl @@ -0,0 +1,2 @@ +ReportGenerator.Reporting.XML +ReportGenerator.XML \ No newline at end of file