Repository: nipreps/nireports (language: python)

Issue:
Set up CI on CircleCI for artifact visualization
### What would you like to see added in this software?

Continuing with https://github.com/nipreps/nireports/issues/6#issuecomment-1151316508 and https://github.com/nipreps/nireports/issues/6#issuecomment-1159040752, we should replicate other nipreps' infrastructure on Circle to enable easy visualization of the test artifacts.

### Do you have any interest in helping implement the feature?

Yes

### Additional information / screenshots

_No response_
Creating a lightweight package easily importable from NiPreps
e.g., MRIQC and fMRIPrep.


Discussion / hints:
@esavary I'm assigning this one to you -- happy to help from the beginning (meaning, please don't try before I give you some pointers).
 @eilidhmacnicol I can help here if needed
Okay, with some pre-releases already on pypi, I think we can call this done.

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6554e1f4..75ef6603 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,26 +1,95 @@
-# Use the latest 2.1 version of CircleCI pipeline process engine.
-# See: https://circleci.com/docs/2.0/configuration-reference
 version: 2.1
+orbs:
+  docker: circleci/docker@1.6.0
 
-# Define a job to be invoked later in a workflow.
-# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
 jobs:
-  say-hello:
-    # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
-    # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
-    docker:
-      - image: cimg/base:stable
-    # Add steps to the job
-    # See: https://circleci.com/docs/2.0/configuration-reference/#steps
+  test_pytest:
+    docker: # executor type
+      - image: nipreps/miniconda:py39_2209.01
+        auth:
+          username: $DOCKER_USER
+          password: $DOCKER_PAT
+
+    environment:
+      - TEST_DIR: /tmp/tests
     steps:
       - checkout
       - run:
-          name: "Say hello"
-          command: "echo Hello, World!"
+          name: Get codecov
+          command: python -m pip install codecov
+
+      - run:
+          name: Run unit tests
+          no_output_timeout: 2h
+          command: |
+            export TEST_OUTPUT_DIR=${TEST_DIR}/output
+            mkdir -p ${TEST_OUTPUT_DIR} ${TEST_DIR}/summary ${TEST_DIR}/coverage
+            python -m pytest --junit-xml=${TEST_DIR}/summary/pytest.xml \
+                   --cov nireports --cov-report xml:${TEST_DIR}/coverage/unittests.xml \
+                   nireports/
+
+      - store_artifacts:
+          path: /tmp/tests/output
+
+      - store_test_results:
+          path: /tmp/tests/summary/
+
+      - run:
+          name: Submit unit test coverage
+          command: |
+            python -m codecov --file ${TEST_DIR}/coverage/unittests.xml \
+                --flags unittests -e CIRCLE_JOB
+
+
+  build_docs:
+    docker: # executor type
+      - image: nipreps/miniconda:py39_2209.01
+        auth:
+          username: $DOCKER_USER
+          password: $DOCKER_PAT
+
+    environment:
+      - FSLOUTPUTTYPE: NIFTI
+      - SUBJECTS_DIR: /tmp/subjects
+    steps:
+      - checkout
+      - run:
+          name: Create subjects folder
+          command: mkdir -p $SUBJECTS_DIR
+      - run:
+          name: Install deps
+          command: |
+            python -m pip install --no-cache-dir -U "pip>=20.3"
+      - run:
+          name: Install Nireports
+          command: |
+            python -m pip install .[docs]
+      - run:
+          name: Build only this commit
+          command: |
+            BRANCH=$( echo $CIRCLE_BRANCH | sed 's+/+_+g' )
+            make -C docs SPHINXOPTS="-W" BUILDDIR="/tmp/docs" OUTDIR=${CIRCLE_TAG:-$BRANCH} html
+      - store_artifacts:
+          path: /tmp/docs
 
-# Invoke jobs via workflows
-# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
 workflows:
-  say-hello-workflow:
+  version: 2
+  build_test_deploy:
     jobs:
-      - say-hello
+      - test_pytest:
+          context:
+            - nipreps-common
+          filters:
+            tags:
+              only: /.*/
+            branches:
+              ignore:
+                - /docs?\/.*/
+
+      - build_docs:
+          filters:
+            branches:
+              ignore:
+                - /tests?\/.*/
+            tags:
+              only: /.*/


Test patch:
diff --git a/nireports/tests/test_reportlets.py b/nireports/tests/test_reportlets.py
index 1c721352..024172e4 100644
--- a/nireports/tests/test_reportlets.py
+++ b/nireports/tests/test_reportlets.py
@@ -24,22 +24,20 @@
 import os
 from pathlib import Path
 
-import numpy as np
 import nibabel as nb
+import numpy as np
 import pandas as pd
 import pytest
 
-from .generate_data import _create_dtseries_cifti
-from nireports.tools.timeseries import (
-    get_tr as _get_tr,
-    cifti_timeseries as _cifti_timeseries,
-    nifti_timeseries as _nifti_timeseries,
-)
-
+from nireports.reportlets.modality.func import fMRIPlot
 from nireports.reportlets.nuisance import plot_carpet
 from nireports.reportlets.surface import cifti_surfaces_plot
 from nireports.reportlets.xca import compcor_variance_plot, plot_melodic_components
-from nireports.reportlets.modality.func import fMRIPlot
+from nireports.tools.timeseries import cifti_timeseries as _cifti_timeseries
+from nireports.tools.timeseries import get_tr as _get_tr
+from nireports.tools.timeseries import nifti_timeseries as _nifti_timeseries
+
+from .generate_data import _create_dtseries_cifti
 
 
 @pytest.mark.parametrize("tr", (None, 0.7))
@@ -54,9 +52,13 @@ def test_carpetplot(tr, sorting, outdir):
         tr=tr,
         output_file=(
             outdir
-            / f"carpet_nosegs_{'index' if tr is None else 'time'}_"
-            / f"{'nosort' if sorting is None else sorting}.svg"
-        ) if outdir is not None else None,
+            / (
+                f"carpet_nosegs_{'index' if tr is None else 'time'}_"
+                f"{'nosort' if sorting is None else sorting}.svg"
+            )
+        )
+        if outdir is not None
+        else None,
         sort_rows=sorting,
         drop_trs=15,
     )
@@ -83,9 +85,13 @@ def test_carpetplot(tr, sorting, outdir):
         tr=tr,
         output_file=(
             outdir
-            / f"carpet_random_{'index' if tr is None else 'seg'}_"
-            / f"{'nosort' if sorting is None else sorting}.svg",
-        ) if outdir is not None else None,
+            / (
+                f"carpet_random_{'index' if tr is None else 'seg'}_"
+                f"{'nosort' if sorting is None else sorting}.svg"
+            )
+        )
+        if outdir is not None
+        else None,
         sort_rows=sorting,
     )
 
@@ -106,9 +112,13 @@ def test_carpetplot(tr, sorting, outdir):
         tr=tr,
         output_file=(
             outdir
-            / f"carpet_const_{'index' if tr is None else 'time'}_"
-            / f"{'nosort' if sorting is None else sorting}.svg"
-        ) if outdir is not None else None,
+            / (
+                f"carpet_const_{'index' if tr is None else 'time'}_"
+                f"{'nosort' if sorting is None else sorting}.svg"
+            )
+        )
+        if outdir is not None
+        else None,
         sort_rows=sorting,
     )