diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -10,8 +10,13 @@
 stages:
   - prepare
   - build
+  - coverage-vis
   - report
 
+'coverage-report-html':
+  stage: coverage-vis
+  image: 'reg.lily.kazv.moe/infra/phorge-ci-tools/pycobertura:servant'
+
 .report:
   image:
     name: 'reg.lily.kazv.moe/infra/phorge-ci-tools:servant'
@@ -22,14 +27,6 @@
   before_script:
     - pipelineUrl="$CI_PROJECT_URL"/-/pipelines/"$CI_PIPELINE_ID"
 
-default:
-  after_script:
-    - touch status.env
-    - 'if [ "$CI_JOB_STATUS" != "success" ]; then echo FAILED=1 >> status.env; fi'
-  artifacts: &defaultArtifacts
-    reports:
-      dotenv: status.env
-
 prepare-env:
   stage: prepare
   script: |
@@ -47,45 +44,50 @@
   stage: build
   image:
     name: 'ubuntu:21.04'
-  script: |
-    ./script.sh
+  script:
+    - apt-get update
+    - apt-get install g++
+    - mkdir build
+    - cd build
+    - g++ -fprofile-arcs -ftest-coverage -fPIC -O0 -o hello ../hello.cpp
+    - ./hello
+    - gcovr --xml-pretty --exclude-unreachable-branches --print-summary -o coverage.xml -r "${CI_PROJECT_DIR}" --object-directory .
+  coverage: /^\s*lines:\s*\d+.\d+\%/
   artifacts:
-    <<: *defaultArtifacts
+    name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
     paths:
-      - testfile
-    expire_in: 1 week
+      - build/coverage.xml
+    reports:
+      coverage_report:
+        coverage_format: cobertura
+        path: build/coverage.xml
 
-build-other:
-  stage: build
-  image:
-    name: 'ubuntu:21.04'
-  script: |
-    ./script.sh
-    false
+'coverage-report-html':
+  stage: coverage-vis
+  image: 'reg.lily.kazv.moe/infra/phorge-ci-tools/pycobertura:servant'
+  script:
+    - pycobertura show ./build/coverage.xml --format html --output ./build/coverage.html --source .
   artifacts:
-    <<: *defaultArtifacts
+    name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
     paths:
-      - testfile
-    expire_in: 1 week
+      - build/coverage.html
 
-another:
-  stage: build
-  image:
-    name: 'ubuntu:21.04'
-  script: |
-    export VAR=foo
-    ./script.sh
-    false
-  artifacts:
-    <<: *defaultArtifacts
-    paths:
-      - testfile
-    expire_in: 1 week
+report-success:
+  extends: .report
+  rules:
+    - if: $TARGET_PHID
+      when: on_success
+    - when: never
+  stage: report
+  script:
+    - 'echo "{\"receiver\": \"$TARGET_PHID\", \"type\": \"pass\"}" | /tools/arcanist/bin/arc call-conduit --conduit-uri https://iron.lily-is.land/ --conduit-token "$CONDUIT_TOKEN" -- harbormaster.sendmessage'
 
-report-final:
+report-failure:
   extends: .report
+  rules:
+    - if: $TARGET_PHID
+      when: on_failure
+    - when: never
   stage: report
   script:
-    - TYPE=pass
-    - if [ -n "$FAILED" ]; then TYPE=fail; fi
-    - 'echo "{\"receiver\": \"$TARGET_PHID\", \"type\": \"$TYPE\"}" | /tools/arcanist/bin/arc call-conduit --conduit-uri https://iron.lily-is.land/ --conduit-token "$CONDUIT_TOKEN" -- harbormaster.sendmessage'
+    - 'echo "{\"receiver\": \"$TARGET_PHID\", \"type\": \"fail\"}" | /tools/arcanist/bin/arc call-conduit --conduit-uri https://iron.lily-is.land/ --conduit-token "$CONDUIT_TOKEN" -- harbormaster.sendmessage'
diff --git a/hello.cpp b/hello.cpp
new file mode 100644
--- /dev/null
+++ b/hello.cpp
@@ -0,0 +1,11 @@
+#include <iostream>
+#include <string>
+
+int main(int argc, char *argv[])
+{
+    if (argv[0] == std::string("hello")) {
+        std::cout << "hello\n";
+    } else {
+        std::cout << "not hello\n";
+    }
+}