123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- #
- #
- # The Nim Tester
- # (c) Copyright 2019 Leorize
- #
- # Look at license.txt for more info.
- # All rights reserved.
- import base64, json, httpclient, os, strutils, uri
- import specs
- const
- RunIdEnv = "TESTAMENT_AZURE_RUN_ID"
- CacheSize = 8 # How many results should be cached before uploading to
- # Azure Pipelines. This prevents throttling that might arise.
- 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
- template getRun(): string =
- ## Get the test run attached to this instance
- getEnv(RunIdEnv)
- template setRun(id: string) =
- ## Attach a test run to this instance and its future children
- putEnv(RunIdEnv, id)
- template delRun() =
- ## Unattach the test run associtated with this instance and its future children
- delEnv(RunIdEnv)
- template warning(args: varargs[untyped]) =
- ## Add a warning to the current task
- stderr.writeLine "##vso[task.logissue type=warning;]", args
- let
- ownRun = not existsEnv RunIdEnv
- ## Whether the test run is owned by this instance
- accessToken = getAzureEnv("System.AccessToken")
- ## Access token to Azure Pipelines
- var
- active = false ## Whether the backend should be activated
- requestBase: Uri ## Base URI for all API requests
- requestHeaders: HttpHeaders ## Headers required for all API requests
- results: JsonNode ## A cache for test results before uploading
- proc request(api: string, httpMethod: HttpMethod, body = ""): Response {.inline.} =
- let client = newHttpClient(timeout = 3000)
- defer: close client
- result = client.request($(requestBase / api), httpMethod, body, requestHeaders)
- if result.code != Http200:
- raise newException(CatchableError, "Request failed")
- proc init*() =
- ## Initialize the Azure Pipelines backend.
- ##
- ## If an access token is provided and no test run is associated with the
- ## current instance, this proc will create a test run named after the current
- ## Azure Pipelines' job name, then associate it to the current testament
- ## instance and its future children. Should this fail, the backend will be
- ## disabled.
- if isAzure and accessToken.len > 0:
- active = true
- requestBase = parseUri(getAzureEnv("System.TeamFoundationCollectionUri")) /
- getAzureEnv("System.TeamProjectId") / "_apis" ? {"api-version": "5.0"}
- requestHeaders = newHttpHeaders {
- "Accept": "application/json",
- "Authorization": "Basic " & encode(':' & accessToken),
- "Content-Type": "application/json"
- }
- results = newJArray()
- if ownRun:
- try:
- let resp = request(
- "test/runs",
- HttpPost,
- $ %* {
- "automated": true,
- "build": { "id": getAzureEnv("Build.BuildId") },
- "buildPlatform": hostCPU,
- "controller": "nim-testament",
- "name": getAzureEnv("Agent.JobName")
- }
- )
- setRun $resp.body.parseJson["id"].getInt
- except:
- warning "Couldn't create test run for Azure Pipelines integration"
- # Set run id to empty to prevent child processes from trying to request
- # for yet another test run id, which wouldn't be shared with other
- # instances.
- setRun ""
- active = false
- elif getRun().len == 0:
- # Disable integration if there aren't any valid test run id
- active = false
- proc uploadAndClear() =
- ## Upload test results from cache to Azure Pipelines. Then clear the cache
- ## after.
- if results.len > 0:
- try:
- discard request("test/runs/" & getRun() & "/results", HttpPost, $results)
- except:
- for i in results:
- warning "Couldn't log test result to Azure Pipelines: ",
- i["automatedTestName"], ", outcome: ", i["outcome"]
- results = newJArray()
- proc finalize*() {.noconv.} =
- ## Finalize the Azure Pipelines backend.
- ##
- ## If a test run has been associated and is owned by this instance, it will
- ## be marked as complete.
- if active:
- if ownRun:
- uploadAndClear()
- try:
- discard request("test/runs/" & getRun(), HttpPatch,
- $ %* {"state": "Completed"})
- except:
- warning "Couldn't update test run ", getRun(), " on Azure Pipelines"
- delRun()
- proc addTestResult*(name, category: string; durationInMs: int; errorMsg: string;
- outcome: TResultEnum) =
- if not active:
- return
- let outcome = case outcome
- of reSuccess: "Passed"
- of reDisabled, reJoined: "NotExecuted"
- else: "Failed"
- results.add(%* {
- "automatedTestName": name,
- "automatedTestStorage": category,
- "durationInMs": durationInMs,
- "errorMessage": errorMsg,
- "outcome": outcome,
- "testCaseTitle": name
- })
- if results.len > CacheSize:
- uploadAndClear()
|