From 2110b240902dc0a105f4ed80062ef062b1033ea2 Mon Sep 17 00:00:00 2001 From: Mike Ellery Date: Fri, 4 Oct 2019 14:19:58 -0700 Subject: [PATCH] Add omitted unit tests, cleanup old files --- .codecov.yml | 3 +- Builds/CMake/RippleConfig.cmake | 2 +- Builds/CMake/RippledCore.cmake | 7 + Jenkinsfile | 694 -------------------------------- src/test/rpc/Roles_test.cpp | 1 + 5 files changed, 10 insertions(+), 697 deletions(-) delete mode 100644 Jenkinsfile diff --git a/.codecov.yml b/.codecov.yml index 2d135d55ed..b2bc67814e 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,6 +1,5 @@ codecov: ci: - - ci.ops.ripple.com # add custom jenkins server - !appveyor - - !travis + - travis diff --git a/Builds/CMake/RippleConfig.cmake b/Builds/CMake/RippleConfig.cmake index 2cb2f2731a..1091e741e7 100644 --- a/Builds/CMake/RippleConfig.cmake +++ b/Builds/CMake/RippleConfig.cmake @@ -12,7 +12,7 @@ if (static OR MSVC) else () set (Boost_USE_STATIC_RUNTIME OFF) endif () -find_dependency (Boost 1.67 +find_dependency (Boost 1.70 COMPONENTS chrono context diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 8a4248a0f6..888fcc7c35 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -392,6 +392,7 @@ if (unity) src/test/unity/crypto_test_unity.cpp src/test/unity/json_test_unity.cpp src/test/unity/ledger_test_unity.cpp + src/test/unity/net_test_unity.cpp src/test/unity/nodestore_test_unity.cpp src/test/unity/overlay_test_unity.cpp src/test/unity/peerfinder_test_unity.cpp @@ -897,6 +898,11 @@ else () src/test/ledger/PendingSaves_test.cpp src/test/ledger/SkipList_test.cpp src/test/ledger/View_test.cpp + #[===============================[ + nounity, test sources: + subdir: net + #]===============================] + src/test/net/SSLHTTPDownloader_test.cpp #[===============================[ nounity, test sources: subdir: nodestore @@ -974,6 +980,7 @@ else () src/test/rpc/NoRipple_test.cpp src/test/rpc/OwnerInfo_test.cpp src/test/rpc/Peers_test.cpp + src/test/rpc/Roles_test.cpp src/test/rpc/RPCCall_test.cpp src/test/rpc/RPCOverload_test.cpp src/test/rpc/RobustTransaction_test.cpp diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 168acb4bc9..0000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,694 +0,0 @@ -#!/usr/bin/env groovy - -import groovy.json.JsonOutput -import java.text.* - -all_status = [:] -commit_id = '' -git_fork = 'ripple' -git_repo = 'rippled' -// -// this is not the actual token, but an ID/key into the jenkins -// credential store which httpRequest can access. -// -github_cred = '6bd3f3b9-9a35-493e-8aef-975403c82d3e' -// -// root API url for our repo (default, overriden below) -// -github_api = 'https://api.github.com/repos/ripple/rippled' - -try { - stage ('Startup Checks') { - // here we check the commit author against collaborators - // we need a node to do this because readJSON requires - // a filesystem (although we just pass it text) - node { - checkout scm - commit_id = getCommitID() - // - // NOTE this getUserRemoteConfigs call requires a one-time - // In-process Script Approval (configure jenkins). We need this - // to detect the remote repo to interact with via the github API. - // - def remote_url = scm.getUserRemoteConfigs()[0].getUrl() - if (remote_url) { - echo "GIT URL scm: $remote_url" - git_fork = remote_url.tokenize('/')[2] - git_repo = remote_url.tokenize('/')[3].split('\\.')[0] - echo "GIT FORK: $git_fork" - echo "GIT REPO: $git_repo" - github_api = "https://api.github.com/repos/${git_fork}/${git_repo}" - echo "API URL REPO: $github_api" - } - - if (env.CHANGE_AUTHOR) { - def collab_found = false; - // - // this means we have some sort of PR , so verify the author - // - echo "CHANGE AUTHOR ---> $CHANGE_AUTHOR" - echo "CHANGE TARGET ---> $CHANGE_TARGET" - echo "CHANGE ID ---> $CHANGE_ID" - // - // check the commit author against collaborators - // we need a node to do this because readJSON requires - // a filesystem (although we just pass it text) - // - def response = httpRequest( - timeout: 10, - authentication: github_cred, - url: "${github_api}/collaborators") - def collab_data = readJSON( - text: response.content) - for (collaborator in collab_data) { - if (collaborator['login'] == "$CHANGE_AUTHOR") { - echo "$CHANGE_AUTHOR is a collaborator!" - collab_found = true; - break; - } - } - - if (! collab_found) { - echo "$CHANGE_AUTHOR is not a collaborator - waiting for manual approval." - sendToSlack("A <${env.BUILD_URL}|jenkins job (PR)> is waiting for approval - please review.") - - try { - httpRequest( - timeout: 10, - authentication: github_cred, - url: getCommentURL(), - contentType: 'APPLICATION_JSON', - httpMode: 'POST', - requestBody: JsonOutput.toJson([ - body: """ -**Thank you** for your submission. It will be reviewed soon and submitted for processing in CI. -""" - ]) - ) - } - catch (e) { - echo 'had a problem interacting with github...comments are probably not updated' - } - - try { - input ( - message: "User $CHANGE_AUTHOR has submitted PR #$CHANGE_ID. " + - "**Please review** the changes for any CI/security concerns " + - "and then decide whether to proceed with building.") - } - catch(e) { - def user = e.getCauses()[0].getUser().toString() - all_status['startup'] = [ - false, - 'Approval Check', - "Build aborted by [${user}]", - "[console](${env.BUILD_URL}/console)"] - error "Aborted by: [${user}]" - } - } - } - } - - // UNCOMMENT the following if we want one message for every job... - //node('rippled-dev') { - // sendToSlack("<${env.BUILD_URL}|Job ${env.BUILD_TAG}> has started.") - //} - } - - stage ('Parallel Build') { - String[][] variants = [ - ['gcc.Release' ,'-Dassert=ON' ,'MANUAL_TESTS=true' ], - ['docs' ,'' ,'TARGET=docs' ], - ['msvc.Debug' ], - ['msvc.Debug' ,'' ,'NINJA_BUILD=true' ], - ['msvc.Debug' ,'-Dunity=OFF' ], - ['msvc.Release' ], - ['gcc.Debug' ], - ['gcc.Debug' ,'-Dunity=OFF' ], - ['gcc.Release' ,'-Dassert=ON' ], - ['gcc.Debug' ,'-Dstatic=OFF' ], - ['gcc.Debug' ,'-Dstatic=OFF -DBUILD_SHARED_LIBS=ON' ], - ['gcc.Debug' ,'' ,'NINJA_BUILD=true' ], - ] - - // create a map of all builds - // that we want to run. The map - // is string keys and node{} object values - def builds = [:] - for (int index = 0; index < variants.size(); index++) { - def bldtype = variants[index][0] - def cmake_extra = variants[index].size() > 1 ? variants[index][1] : '' - def bldlabel = bldtype + cmake_extra - def extra_env = variants[index].size() > 2 ? variants[index][2..-1] : [] - for (int j = 0; j < extra_env.size(); j++) { - bldlabel += "_" + extra_env[j] - } - bldlabel = bldlabel.replace('-', '_') - bldlabel = bldlabel.replace(' ', '') - bldlabel = bldlabel.replace('=', '_') - - def compiler = getFirstPart(bldtype) - def config = getSecondPart(bldtype) - def target = 'install' // currently ignored for windows builds - if (compiler == 'docs') { - compiler = 'gcc' - config = 'Release' - target = 'docs' - } - def cc = 'gcc' - def cxx = 'g++' - def ucc = isNoUnity(cmake_extra) ? 'true' : 'false' - def node_type = - (compiler == 'msvc') ? 'rippled-win' : 'rippled-dev' - // the default disposition for parallel test..disabled - // for coverage, enabled otherwise. Can still be overridden - // by explicitly setting with extra env settings above. - def pt = isCoverage(cmake_extra) ? 'false' : 'true' - def max_minutes = 25 - - def env_vars = [ - "TARGET=${target}", - "BUILD_TYPE=${config}", - "COMPILER=${compiler}", - "PARALLEL_TESTS=${pt}", - 'BUILD=cmake', - "MAX_TIME=${max_minutes}m", - "BUILD_DIR=${bldlabel}", - "CMAKE_EXTRA_ARGS=-Dwerr=ON ${cmake_extra}", - 'VERBOSE_BUILD=true'] - - builds[bldlabel] = { - node(node_type) { - checkout scm - dir ('build') { - deleteDir() - } - def cdir = upDir(pwd()) - echo "BASEDIR: ${cdir}" - echo "COMPILER: ${compiler}" - echo "BUILD_TYPE: ${config}" - echo "USE_CC: ${ucc}" - env_vars.addAll([ - "NIH_CACHE_ROOT=${cdir}/.nih_c"]) - if (compiler == 'msvc') { - env_vars.addAll([ - 'BOOST_ROOT=c:\\lib\\boost_1_70', - 'PROJECT_NAME=rippled', - 'MSBUILDDISABLENODEREUSE=1', // this ENV setting is probably redundant since we also pass /nr:false to msbuild - 'OPENSSL_ROOT=c:\\OpenSSL-Win64']) - } - else { - env_vars.addAll([ - 'NINJA_BUILD=false', - "CCACHE_BASEDIR=${cdir}", - 'PLANTUML_JAR=/opt/plantuml/plantuml.jar', - 'APP_ARGS=--unittest-ipv6', - 'CCACHE_NOHASHDIR=true', - "CC=${cc}", - "CXX=${cxx}", - 'LCOV_ROOT=""', - 'PATH+CMAKE_BIN=/opt/local/cmake', - 'GDB_ROOT=/opt/local/gdb', - 'BOOST_ROOT=/opt/local/boost_1_70_0', - "USE_CCACHE=${ucc}"]) - } - - if (extra_env.size() > 0) { - env_vars.addAll(extra_env) - } - - // try to figure out codecov token to use. Look for - // MY_CODECOV_TOKEN id first so users can set that - // on job scope but then default to RIPPLED_CODECOV_TOKEN - // which should be globally scoped - def codecov_token = '' - try { - withCredentials( [string( credentialsId: 'MY_CODECOV_TOKEN', variable: 'CODECOV_TOKEN')]) { - codecov_token = env.CODECOV_TOKEN - } - } - catch (e) { - // this might throw when MY_CODECOV_TOKEN doesn't exist - } - if (codecov_token == '') { - withCredentials( [string( credentialsId: 'RIPPLED_CODECOV_TOKEN', variable: 'CODECOV_TOKEN')]) { - codecov_token = env.CODECOV_TOKEN - } - } - env_vars.addAll(["CODECOV_TOKEN=${codecov_token}"]) - - withEnv(env_vars) { - myStage(bldlabel) - def thrown = ''; - try { - timeout( - time: max_minutes * 2, - units: 'MINUTES') - { - if (compiler == 'msvc') { - powershell "Remove-Item -Path \"${bldlabel}.txt\" -Force -ErrorAction Ignore" - // we capture stdout to variable because I could - // not figure out how to make powershell redirect internally - output = powershell ( - returnStdout: true, - script: windowsBuildCmd()) - // if the powershell command fails (has nonzero exit) - // then the command above throws, we don't get our output, - // and we never create this output file. - // SEE https://issues.jenkins-ci.org/browse/JENKINS-44930 - // Alternatively, figure out how to reliably redirect - // all output above to a file (Start/Stop transcript does not work) - writeFile( - file: "${bldlabel}.txt", - text: output) - } - else { - sh "rm -fv ${bldlabel}.txt" - // execute the bld command in a redirecting shell - // to capture output - sh redhatBuildCmd(bldlabel) - } - } - } - catch(e) { - thrown = "${e}" - throw e - } - finally { - if (bldtype == 'docs') { - publishHTML( - allowMissing: true, - alwaysLinkToLastBuild: true, - keepAll: true, - reportName: 'Doxygen', - reportDir: "build/${bldlabel}/html_doc", - reportFiles: 'index.html') - } - if (isCoverage(cmake_extra)) { - publishHTML( - allowMissing: true, - alwaysLinkToLastBuild: false, - keepAll: true, - reportName: 'Coverage', - reportDir: "build/${bldlabel}/coverage", - reportFiles: 'index.html') - } - def envs = '' - for (int j = 0; j < extra_env.size(); j++) { - envs += ",
" + extra_env[j] - } - def cmake_txt = cmake_extra - if (cmake_txt != '') { - cmake_txt = "
" + cmake_txt - } - def st = reportStatus(bldlabel, bldtype + cmake_txt + envs, env.BUILD_URL, thrown) - lock('rippled_dev_status') { - all_status[bldlabel] = st - } - if (thrown == '') { - assert st[0] : "Unit Test Failures" - } - } //try-catch-finally - } //withEnv - } //node - } //builds item - } //for variants - - // this actually executes all the builds we just defined - // above, in parallel as slaves are available - parallel builds - } -} -finally { - // anything here should run always... - stage ('Final Status') { - node { - def results = makeResultText() - try { - def res = getCommentID() //get array return b/c jenkins does not allow multiple direct return/assign - def comment_id = res[0] - def url_comment = res[1] - def mode = 'PATCH' - if (comment_id == 0) { - echo 'no existing status comment found' - mode = 'POST' - } - - def body = JsonOutput.toJson([ - body: results - ]) - - response = httpRequest( - timeout: 10, - authentication: github_cred, - url: url_comment, - contentType: 'APPLICATION_JSON', - httpMode: mode, - requestBody: body) - } - catch (e) { - echo 'had a problem interacting with github...status is probably not updated' - } - } - } -} - -// --------------- -// util functions -// --------------- -def myStage(name) { - echo """ -+++++++++++++++++++++++++++++++++++++++++ - >> building ${name} -+++++++++++++++++++++++++++++++++++++++++ -""" -} - -def printGitInfo(id, log) { - echo """ -+++++++++++++++++++++++++++++++++++++++++ - >> Building commit ID ${id} - >> -${log} -+++++++++++++++++++++++++++++++++++++++++ -""" -} - -def makeResultText () { - def start_time = new Date() - def sdf = new SimpleDateFormat('yyyyMMdd - HH:mm:ss') - def datestamp = sdf.format(start_time) - - def results = """ -## Jenkins Build Summary - -Built from [this commit](https://github.com/${git_fork}/${git_repo}/commit/${commit_id}) - -Built at __${datestamp}__ - -### Test Results - -Build Type | Log | Result | Status ----------- | --- | ------ | ------ -""" - for ( e in all_status) { - results += e.value[1] + ' | ' + e.value[3] + ' | ' + e.value[2] + ' | ' + - (e.value[0] ? 'PASS :white_check_mark: ' : 'FAIL :red_circle: ') + '\n' - } - results += '\n' - echo 'FINAL BUILD RESULTS' - echo results - results -} - -def getCommentURL () { - def url_c = '' - if (env.CHANGE_ID && env.CHANGE_ID ==~ /\d+/) { - // - // CHANGE_ID indicates we are building a PR - // find PR comments - // - def resp = httpRequest( - timeout: 10, - authentication: github_cred, - url: "${github_api}/pulls/$CHANGE_ID") - def result = readJSON(text: resp.content) - // - // follow issue comments link - // - url_c = result['_links']['issue']['href'] + '/comments' - } - else { - // - // if not a PR, just search comments for our commit ID - // - url_c = - "${github_api}/commits/${commit_id}/comments" - } - url_c -} - -def getCommentID () { - def url_c = getCommentURL() - def response = httpRequest( - timeout: 10, - authentication: github_cred, - url: url_c) - def data = readJSON(text: response.content) - def comment_id = 0 - // see if we can find and existing comment here with - // a heading that matches ours... - for (comment in data) { - if (comment['body'] =~ /(?m)^##\s+Jenkins Build/) { - comment_id = comment['id'] - echo "existing status comment ${comment_id} found" - url_c = comment['url'] - break; - } - } - [comment_id, url_c] -} - -def getCommitID () { - def cid = sh ( - script: 'git rev-parse HEAD', - returnStdout: true) - cid = cid.trim() - echo "commit ID is ${cid}" - commit_log = sh ( - script: "git show --name-status ${cid}", - returnStdout: true) - printGitInfo (cid, commit_log) - cid -} - -@NonCPS -def getResults(text, label) { - // example: - /// 194.5s, 154 suites, 948 cases, 360485 tests total, 0 failures - // or build log format: - // [msvc.release] 71.3s, 162 suites, 995 cases, 318901 tests total, 1 failure - def matcher = - text == '' ? - manager.getLogMatcher(/\[${label}\].+?(\d+) case[s]?, (\d+) test[s]? total, (\d+) (failure(s?))/) : - text =~ /(\d+) case[s]?, (\d+) test[s]? total, (\d+) (failure(s?))/ - matcher ? matcher[0][1] + ' cases, ' + matcher[0][3] + ' failed' : 'no test results' -} - -@NonCPS -def getFailures(text, label) { - // [see above for format] - def matcher = - text == '' ? - manager.getLogMatcher(/\[${label}\].+?(\d+) test[s]? total, (\d+) (failure(s?))/) : - text =~ /(\d+) test[s]? total, (\d+) (failure(s?))/ - // if we didn't match, then return -1 since something is - // probably wrong, e.g. maybe the build failed... - matcher ? matcher[0][2] as Integer : -1i -} - -@NonCPS -def getTime(text, label) { - // look for text following a label 'real' for - // wallclock time. Some `time`s report fractional - // seconds and we can omit those in what we report - def matcher = - text == '' ? - manager.getLogMatcher(/(?m)^\[${label}\]\s+real\s+(.+)\.(\d+?)[s]?/) : - text =~ /(?m)^real\s+(.+)\.(\d+?)[s]?/ - if (matcher) { - return matcher[0][1] + 's' - } - - // alternatively, look for powershell elapsed time - // format, e.g. : - // TotalSeconds : 523.2140529 - def matcher2 = - text == '' ? - manager.getLogMatcher(/(?m)^\[${label}\]\s+TotalSeconds\s+:\s+(\d+)\.(\d+?)?/) : - text =~ /(?m)^TotalSeconds\s+:\s+(\d+)\.(\d+?)?/ - matcher2 ? matcher2[0][1] + 's' : 'n/a' -} - -@NonCPS -def getFirstPart(bld) { - def matcher = bld =~ /^(.+?)\.(.+)$/ - matcher ? matcher[0][1] : bld -} - -@NonCPS -def isNoUnity(bld) { - def matcher = bld =~ /-Dunity=(off|OFF)/ - matcher ? true : false -} - -@NonCPS -def isCoverage(bld) { - def matcher = bld =~ /-Dcoverage=(on|ON)/ - matcher ? true : false -} - -@NonCPS -def getSecondPart(bld) { - def matcher = bld =~ /^(.+?)\.(.+)$/ - matcher ? matcher[0][2] : bld -} - -// because I can't seem to find path manipulation -// functions in groovy.... -@NonCPS -def upDir(path) { - def matcher = path =~ /^(.+)[\/\\](.+?)/ - matcher ? matcher[0][1] : path -} - -def sendToSlack(message) { - try { - withCredentials( [string( credentialsId: 'RIPPLED_SLACK_INCOMING_URL', variable: 'SLACK_URL')]) { - // I was unable to make httpRequest method work with the - // formdata required by slack API, so resorting - // to curl commands... - sh '''\ -CONTENT=$(tr -d '[\n]' <&1 1>>${log_file} 2>&1 -ccache -s -source /opt/rh/devtoolset-7/enable -/usr/bin/time -p ./bin/ci/ubuntu/build-and-test.sh 2>&1 -ccache -s -''' -} - -// the powershell command used for building -def windowsBuildCmd() { -''' -# Enable streams 3-6 -$WarningPreference = 'Continue' -$VerbosePreference = 'Continue' -$DebugPreference = 'Continue' -$InformationPreference = 'Continue' - -Invoke-BatchFile "${env:ProgramFiles(x86)}\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat" x86_amd64 -Get-ChildItem env:* | Sort-Object name -cl -cmake --version -New-Item -ItemType Directory -Force -Path "build/$env:BUILD_DIR" -ErrorAction Stop -$sw = [Diagnostics.Stopwatch]::StartNew() -try { - Push-Location "build/$env:BUILD_DIR" - if ($env:NINJA_BUILD -eq "true") { - Invoke-Expression "& cmake -G`"Ninja`" -DCMAKE_BUILD_TYPE=$env:BUILD_TYPE -DCMAKE_VERBOSE_MAKEFILE=ON $env:CMAKE_EXTRA_ARGS ../.." - } - else { - Invoke-Expression "& cmake -G`"Visual Studio 15 2017 Win64`" -DCMAKE_VERBOSE_MAKEFILE=ON $env:CMAKE_EXTRA_ARGS ../.." - } - if ($LastExitCode -ne 0) { throw "CMake failed" } - - ## as of 01/2018, DO NOT USE cmake to run the actual build step. for some - ## reason, cmake spawning the build under jenkins causes MSBUILD/ninja to - ## get stuck at the end of the build. Perhaps cmake is spawning - ## incorrectly or failing to pass certain params - - if ($env:NINJA_BUILD -eq "true") { - ninja -j $env:NUMBER_OF_PROCESSORS -v - } - else { - msbuild /fl /m /nr:false /p:Configuration="$env:BUILD_TYPE" /p:Platform=x64 /p:GenerateFullPaths=True /v:normal /nologo /clp:"ShowCommandLine;DisableConsoleColor" "$env:PROJECT_NAME.vcxproj" - } - if ($LastExitCode -ne 0) { throw "CMake build failed" } - - $exe = "./$env:BUILD_TYPE/$env:PROJECT_NAME" - if ($env:NINJA_BUILD -eq "true") { - $exe = "./$env:PROJECT_NAME" - } - "Exe is at $exe" - $params = '--unittest', '--quiet', '--unittest-log' - if ($env:PARALLEL_TESTS -eq "true") { - $params = $params += "--unittest-jobs=$env:NUMBER_OF_PROCESSORS" - } - & $exe $params - if ($LastExitCode -ne 0) { throw "Unit tests failed" } -} -catch { - throw -} -finally { - $sw.Stop() - $sw.Elapsed - Pop-Location -} -''' -} - -// post processing step after each build: -// * archives the log file -// * adds short description/status to build status -// * returns an array of result info to add to the all_build summary -def reportStatus(label, type, bldurl, errmsg) { - def outstr = '' - def loglink = "[console](${bldurl}/console)" - def logfile = "${label}.txt" - if ( fileExists(logfile) ) { - archiveArtifacts( artifacts: logfile ) - outstr = readFile(logfile) - loglink = "[logfile](${bldurl}/artifact/${logfile})" - } - def st = getResults(outstr, label) - def time = getTime(outstr, label) - def fail_count = getFailures(outstr, label) - outstr = null - def txtcolor = - (fail_count == 0 && errmsg == '') ? 'DarkGreen' : 'Crimson' - def shortbld = label - // this is just an attempt to shorten the - // summary text label to the point of absurdity.. - shortbld = shortbld.replace('Debug', 'dbg') - shortbld = shortbld.replace('Release', 'rel') - shortbld = shortbld.replace('true', 'Y') - shortbld = shortbld.replace('false', 'N') - shortbld = shortbld.replace('Dcoverage', 'cov') - shortbld = shortbld.replace('Dassert', 'asrt') - shortbld = shortbld.replace('Dunity', 'unty') - shortbld = shortbld.replace('Dsan=address', 'asan') - shortbld = shortbld.replace('Dsan=thread', 'tsan') - shortbld = shortbld.replace('Dsan=undefined', 'ubsan') - shortbld = shortbld.replace('PARALLEL_TEST', 'PL') - shortbld = shortbld.replace('MANUAL_TESTS', 'MAN') - shortbld = shortbld.replace('NINJA_BUILD', 'ninja') - shortbld = shortbld.replace('DEBUGGER', 'gdb') - shortbld = shortbld.replace('ON', 'Y') - shortbld = shortbld.replace('OFF', 'N') - def stattext = "${st}, t: ${time}" - if (fail_count <= 0 && errmsg != '') { - stattext += " [BAD EXIT]" - } - manager.addShortText( - "${shortbld}: ${stattext}", - txtcolor, - 'white', - '0px', - 'white') - [fail_count == 0 && errmsg == '', type, stattext, loglink] -} - diff --git a/src/test/rpc/Roles_test.cpp b/src/test/rpc/Roles_test.cpp index c18c798c33..012bec328d 100644 --- a/src/test/rpc/Roles_test.cpp +++ b/src/test/rpc/Roles_test.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include