testament: add azure integration
This commit is contained in:
parent
880df4de62
commit
acebcd7899
4 changed files with 122 additions and 10 deletions
95
testament/azure.nim
Normal file
95
testament/azure.nim
Normal 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()
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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: ",
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
path = "$nim" # For compiler/nodejs
|
path = "$nim" # For compiler/nodejs
|
||||||
|
-d:ssl # For azure
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue