testament: add azure integration

This commit is contained in:
Leorize 2019-10-03 17:36:07 +02:00 • committed by narimiran
commit acebcd7899
4 changed files with 122 additions and 10 deletions

95
testament/azure.nim Normal file
View file

@ -0,0 +1,95 @@
#
#
# The Nim Tester
# (c) Copyright 2019 Leorize
#
# Look at license.txt for more info.
# All rights reserved.
import base64, json, httpclient, os, strutils
import specs
const
ApiRuns = "/_apis/test/runs"
ApiVersion = "?api-version=5.0"
ApiResults = ApiRuns & "/$1/results"
var runId* = -1
proc getAzureEnv(env: string): string =
# Conversion rule at:
# https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables#set-variables-in-pipeline
env.toUpperAscii().replace('.', '_').getEnv
proc invokeRest(httpMethod: HttpMethod; api: string; body = ""): Response =
let http = newHttpClient()
defer: close http
result = http.request(getAzureEnv("System.TeamFoundationCollectionUri") &
getAzureEnv("System.TeamProjectId") & api & ApiVersion,
httpMethod,
$body,
newHttpHeaders {
"Accept": "application/json",
"Authorization": "Basic " & encode(':' & getAzureEnv("System.AccessToken")),
"Content-Type": "application/json"
})
if not result.code.is2xx:
raise newException(HttpRequestError, "Server returned: " & result.body)
proc finish*() {.noconv.} =
if not isAzure or runId < 0:
return
try:
discard invokeRest(HttpPatch,
ApiRuns & "/" & $runId,
$ %* { "state": "Completed" })
except:
stderr.writeLine "##vso[task.logissue type=warning;]Unable to finalize Azure backend"
stderr.writeLine getCurrentExceptionMsg()
runId = -1
# TODO: Only obtain a run id if tests are run
# NOTE: We can't delete test runs with Azure's access token
proc start*() =
if not isAzure:
return
try:
if runId < 0:
runId = invokeRest(HttpPost,
ApiRuns,
$ %* {
"automated": true,
"build": { "id": getAzureEnv("Build.BuildId") },
"buildPlatform": hostCPU,
"controller": "nim-testament",
"name": getAzureEnv("Agent.JobName")
}).body.parseJson["id"].getInt(-1)
except:
stderr.writeLine "##vso[task.logissue type=warning;]Unable to initialize Azure backend"
stderr.writeLine getCurrentExceptionMsg()
proc addTestResult*(name, category: string; durationInMs: int; errorMsg: string;
outcome: TResultEnum) =
if not isAzure or runId < 0:
return
let outcome = case outcome
of reSuccess: "Passed"
of reDisabled, reJoined: "NotExecuted"
else: "Failed"
try:
discard invokeRest(HttpPost,
ApiResults % [$runId],
$ %* [{
"automatedTestName": name,
"automatedTestStorage": category,
"durationInMs": durationInMs,
"errorMessage": errorMsg,
"outcome": outcome,
"testCaseTitle": name
}])
except:
stderr.writeLine "##vso[task.logissue type=warning;]Unable to log test case: ",
name, ", outcome: ", outcome
stderr.writeLine getCurrentExceptionMsg()

View file

@ -13,6 +13,7 @@ var compilerPrefix* = findExe("nim")
let isTravis* = existsEnv("TRAVIS") let isTravis* = existsEnv("TRAVIS")
let isAppVeyor* = existsEnv("APPVEYOR") let isAppVeyor* = existsEnv("APPVEYOR")
let isAzure* = existsEnv("TF_BUILD")
var skips*: seq[string] var skips*: seq[string]
@ -214,6 +215,8 @@ proc parseSpec*(filename: string): TSpec =
if isTravis: result.err = reDisabled if isTravis: result.err = reDisabled
of "appveyor": of "appveyor":
if isAppVeyor: result.err = reDisabled if isAppVeyor: result.err = reDisabled
of "azure":
if isAzure: result.err = reDisabled
of "32bit": of "32bit":
if sizeof(int) == 4: if sizeof(int) == 4:
result.err = reDisabled result.err = reDisabled

View file

@ -12,7 +12,7 @@
import import
strutils, pegs, os, osproc, streams, json, strutils, pegs, os, osproc, streams, json,
backend, parseopt, specs, htmlgen, browsers, terminal, backend, parseopt, specs, htmlgen, browsers, terminal,
algorithm, times, md5, sequtils algorithm, times, md5, sequtils, azure
include compiler/nodejs include compiler/nodejs
@ -284,7 +284,7 @@ proc addResult(r: var TResults, test: TTest, target: TTarget,
maybeStyledEcho styleBright, given, "\n" maybeStyledEcho styleBright, given, "\n"
if backendLogging and existsEnv("APPVEYOR"): if backendLogging and (isAppVeyor or isAzure):
let (outcome, msg) = let (outcome, msg) =
case success case success
of reSuccess: of reSuccess:
@ -295,6 +295,9 @@ proc addResult(r: var TResults, test: TTest, target: TTarget,
("Failed", "Failure: " & $success & "\n" & given) ("Failed", "Failure: " & $success & "\n" & given)
else: else:
("Failed", "Failure: " & $success & "\nExpected:\n" & expected & "\n\n" & "Gotten:\n" & given) ("Failed", "Failure: " & $success & "\nExpected:\n" & expected & "\n\n" & "Gotten:\n" & given)
if isAzure:
azure.addTestResult(name, test.cat.string, int(duration * 1000), msg, success)
else:
var p = startProcess("appveyor", args=["AddTest", test.name.replace("\\", "/") & test.options, var p = startProcess("appveyor", args=["AddTest", test.name.replace("\\", "/") & test.options,
"-Framework", "nim-testament", "-FileName", "-Framework", "nim-testament", "-FileName",
test.cat.string, test.cat.string,
@ -645,6 +648,8 @@ proc main() =
quit Usage quit Usage
of "skipfrom": of "skipfrom":
skipFrom = p.val.string skipFrom = p.val.string
of "azurerunid":
runId = p.val.parseInt
else: else:
quit Usage quit Usage
p.next() p.next()
@ -657,6 +662,7 @@ proc main() =
of "all": of "all":
#processCategory(r, Category"megatest", p.cmdLineRest.string, testsDir, runJoinableTests = false) #processCategory(r, Category"megatest", p.cmdLineRest.string, testsDir, runJoinableTests = false)
azure.start()
var myself = quoteShell(findExe("testament" / "testament")) var myself = quoteShell(findExe("testament" / "testament"))
if targetsStr.len > 0: if targetsStr.len > 0:
myself &= " " & quoteShell("--targets:" & targetsStr) myself &= " " & quoteShell("--targets:" & targetsStr)
@ -665,6 +671,8 @@ proc main() =
if skipFrom.len > 0: if skipFrom.len > 0:
myself &= " " & quoteShell("--skipFrom:" & skipFrom) myself &= " " & quoteShell("--skipFrom:" & skipFrom)
if isAzure:
myself &= " " & quoteShell("--azureRunId:" & $runId)
var cats: seq[string] var cats: seq[string]
let rest = if p.cmdLineRest.string.len > 0: " " & p.cmdLineRest.string else: "" let rest = if p.cmdLineRest.string.len > 0: " " & p.cmdLineRest.string else: ""
@ -690,13 +698,16 @@ proc main() =
progressStatus(i) progressStatus(i)
processCategory(r, Category(cati), p.cmdLineRest.string, testsDir, runJoinableTests = false) processCategory(r, Category(cati), p.cmdLineRest.string, testsDir, runJoinableTests = false)
else: else:
addQuitProc azure.finish
quit osproc.execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, beforeRunEvent = progressStatus) quit osproc.execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, beforeRunEvent = progressStatus)
of "c", "cat", "category": of "c", "cat", "category":
azure.start()
skips = loadSkipFrom(skipFrom) skips = loadSkipFrom(skipFrom)
var cat = Category(p.key) var cat = Category(p.key)
p.next p.next
processCategory(r, cat, p.cmdLineRest.string, testsDir, runJoinableTests = true) processCategory(r, cat, p.cmdLineRest.string, testsDir, runJoinableTests = true)
of "pcat": of "pcat":
azure.start()
skips = loadSkipFrom(skipFrom) skips = loadSkipFrom(skipFrom)
# 'pcat' is used for running a category in parallel. Currently the only # 'pcat' is used for running a category in parallel. Currently the only
# difference is that we don't want to run joinable tests here as they # difference is that we don't want to run joinable tests here as they
@ -711,6 +722,7 @@ proc main() =
p.next p.next
processPattern(r, pattern, p.cmdLineRest.string, simulate) processPattern(r, pattern, p.cmdLineRest.string, simulate)
of "r", "run": of "r", "run":
azure.start()
# at least one directory is required in the path, to use as a category name # at least one directory is required in the path, to use as a category name
let pathParts = split(p.key.string, {DirSep, AltSep}) let pathParts = split(p.key.string, {DirSep, AltSep})
# "stdlib/nre/captures.nim" -> "stdlib" + "nre/captures.nim" # "stdlib/nre/captures.nim" -> "stdlib" + "nre/captures.nim"
@ -726,6 +738,7 @@ proc main() =
if action == "html": openDefaultBrowser(resultsFile) if action == "html": openDefaultBrowser(resultsFile)
else: echo r, r.data else: echo r, r.data
backend.close() backend.close()
if isMainProcess: azure.finish()
var failed = r.total - r.passed - r.skipped var failed = r.total - r.passed - r.skipped
if failed != 0: if failed != 0:
echo "FAILURE! total: ", r.total, " passed: ", r.passed, " skipped: ", echo "FAILURE! total: ", r.total, " passed: ", r.passed, " skipped: ",

View file

@ -1 +1,2 @@
path = "$nim" # For compiler/nodejs path = "$nim" # For compiler/nodejs
-d:ssl # For azure