Skip to content

Commit

Permalink
Use CircleCI for continuous integration testing.
Browse files Browse the repository at this point in the history
The CircleCI configuration includes build, unit testing and storing of
test results in JUnit reports, and downloading a recently built
gem5-aladdin binary so that we can run gem5 simulations in the future as
part of the CI flow.

TESTED=verified CircleCI pipeline passes.
  • Loading branch information
xyzsam committed Jul 27, 2020
1 parent d152f3c commit cc8679c
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 1 deletion.
30 changes: 30 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: 2.1
jobs:
build:
docker:
- image: xyzsam/smaug:latest
environment:
SMAUG_HOME: /root/project
steps:
- checkout
- run:
name: Checkout dependencies
command: git submodule update --init --recursive
- run:
name: Build
command: |
make all -j2
make test -j2
- run:
name: Run unit tests
environment:
JUNIT_REPORT_DIR: ~/smaug-junit-report-dir
command: |
export PYTHONPATH=$SMAUG_HOME:$PYTHONPATH
make test-run-junit
- run:
name: Download latest gem5-aladdin binary
command:
python .circleci/download_artifacts.py --api_token=${GEM5_ALADDIN_BUILD_ARTIFACT_TOKEN} --project=gem5-aladdin --artifact_name=gem5.opt --user=${USER} --download_loc=/tmp --filter=${BUILD_ARTIFACT_FILTER} --branch=${BUILD_ARTIFACT_BRANCH}
- store_test_results:
path: ~/smaug-junit-report-dir
88 changes: 88 additions & 0 deletions .circleci/download_artifacts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Downloads build artifacts via the CircleCI API."""

import argparse
import os
import json
import six
import subprocess
import sys

def query_artifacts(api_token, user, project, branch, filter):
"""Queries CircleCI for a list of build artifacts.
All parameters are strings. The returned value is a JSON object with the
following attributes:
"path": Path to the artifact in the project, relative to the working
directory.
"pretty_path": Same as path.
"node_index": Ignored.
"url": The URL at which the artifact is located.
"""
url = "https://circleci.com/api/v1.1/project/github/%(user)s/%(project)s/latest/artifacts?branch=%(branch)s&filter=%(filter)s" % {
"user": user,
"project": project,
"branch": branch,
"filter": filter,
}
args = ["curl", "-H'Circle-Token: %s'" % api_token, url]
print(" ".join(args))
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
print("Stdout: %s", stdout)
print("Stderr: %s", stderr)
if proc.returncode != 0:
raise OSError("artifact lookup failed")
artifacts = json.loads(stdout)
return artifacts


def download_artifacts(artifacts, artifact_name, download_loc):
"""Downloads matching artifacts to the desired location.
artifacts: List of artifacts from the CircleCI API.
artifact_name: The basename of the artifacts to download.
download_loc: The directory where the objects will be downloaded to.
"""
for artifact in artifacts:
if os.path.basename(artifact["path"]) == artifact_name:
proc = subprocess.Popen(["wget", "-P", download_loc, artifact["url"]])
stdout, stderr = proc.communicate()
if stdout is not None:
print(six.ensure_str(stdout))
if stderr is not None:
print(six.ensure_str(stderr))
if proc.returncode != 0:
raise OSError("failed to download artifact")


def main():
parser = argparse.ArgumentParser(
"Download CircleCI artifacts for SMAUG dependencies")
parser.add_argument("--artifact_name", type=str, required=True,
help="Base filename of the object to download.")
parser.add_argument("--download_loc", type=str, required=True,
help="Directory to store the downloaded artifact.")
parser.add_argument("--api_token", type=str, required=True,
help="Secret API token for access to CircleCI APIs.")
parser.add_argument("--project", type=str, required=True,
help="Name of the project to download from")
parser.add_argument("--user", type=str, default="harvard-acc",
help="CircleCI username.")
parser.add_argument("--branch", type=str, default="master",
help="Branch from which the artifact was built.")
parser.add_argument("--filter", type=str, default="successful",
help="Filter on which builds to return.")
args = parser.parse_args()
artifacts = query_artifacts(args.api_token,
args.user,
args.project,
args.branch,
args.filter)
if "message" in artifacts:
print("Artifact query failed:", artifacts["message"])
sys.exit(1)
download_artifacts(artifacts, args.artifact_name, args.download_loc)


if __name__ == "__main__":
main()
26 changes: 26 additions & 0 deletions .circleci/run_cpp_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash
# Runs C++ Catch2 tests with Junit reports.
# Usage:
# ./run_cpp_tests.sh report_dir test0 test1 test2...

REPORT_DIR=$1
TESTS=${@:2} # All remaining arguments
HAS_ERROR=0

mkdir -p ${REPORT_DIR}
for t in $TESTS; do
printf "Running test ($t)"
JUNIT_XML=$(basename $t).xml
./$t -r junit -o ${REPORT_DIR}/${JUNIT_XML}
RET_VAL=$(echo $?)
if [ $RET_VAL -ne 0 ]; then
echo "... failed"
HAS_ERROR=1
else
echo " ... ok"
fi
done

if [ $HAS_ERROR -ne 0 ]; then
exit 1
fi
30 changes: 30 additions & 0 deletions .circleci/run_py_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
# Runs Python tests without JUnit reports.
# Usage:
# ./run_py_tests.sh report_dir test0 test1 test2...
#
# No reports are currently generated but this is planned.

REPORT_DIR=$1
TESTS=${@:2}
HAS_ERROR=0

mkdir -p ${REPORT_DIR}
for t in $TESTS; do
printf "Running test ($t)"
JUNIT_XML=$(basename $t).xml
./$t > tmpout 2>&1
RET_VAL=$(echo $?)
if [ $RET_VAL -ne 0 ]; then
echo "... failed"
cat tmpout
HAS_ERROR=1
else
echo " ... ok"
fi
done
rm -f tmpout

if [ $HAS_ERROR -ne 0 ]; then
exit 1
fi
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ test:
@$(MAKE) -f make/Makefile.native --no-print-directory tests
test-run:
@$(MAKE) -f make/Makefile.native --no-print-directory run-tests
test-run-junit:
@$(MAKE) -f make/Makefile.native --no-print-directory run-tests-junit
clean:
@$(MAKE) -f make/Makefile.native --no-print-directory clean
tracer:
Expand Down
19 changes: 18 additions & 1 deletion make/Makefile.native
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

include make/Makefile.common

.PHONY: all tests clean run-tests
.PHONY: all tests clean run-tests run-tests-junit run-cpp-tests run-py-tests

SHELL:=/bin/bash

####################################
#### COMPILATION FLAGS ####
Expand Down Expand Up @@ -91,6 +93,21 @@ run-tests:
exit 1; \
fi

# For CI. JUNIT_REPORT_DIR is set by the CI engine.
run-tests-junit:
@$(MAKE) -f make/Makefile.native --no-print-directory run-cpp-tests
@$(MAKE) -f make/Makefile.native --no-print-directory run-py-tests

run-cpp-tests:
@$(MAKE) -f make/Makefile.native --no-print-directory tests
@$(MAKE) -f make/Makefile.native --no-print-directory exec
.circleci/run_cpp_tests.sh $(JUNIT_REPORT_DIR) $(TEST_BIN)

run-py-tests:
@$(MAKE) -f make/Makefile.native --no-print-directory tests
@$(MAKE) -f make/Makefile.native --no-print-directory exec
.circleci/run_py_tests.sh $(JUNIT_REPORT_DIR) $(BUILD_PY_TESTS)

###########################
#### CLEAN UP ####
###########################
Expand Down

0 comments on commit cc8679c

Please sign in to comment.