chore(qol): Test python and shell code samples. WIP - use for local testing only, not ready for CI. (#5154)
- Adds a Docker image, scripts, and configuration that install and run pytest-codeblocks on Python and shell code samples in Markdown. - Copied from https://github.com/jstirnaman/docs-codeblocks/ - TODO: The script copies files into a tmp directory configured as a bind mount for the Docker container. We might want to replace this with the experimental `watch` feature. - TODO: Use a secret to configure the token. As is, values subbed from the dotenv file are exposed in your Docker container history.pull/5157/head
parent
e278c0cd42
commit
237b01877b
|
|
@ -0,0 +1,47 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
# If you need more help, visit the Dockerfile reference guide at
|
||||
# https://docs.docker.com/engine/reference/builder/
|
||||
|
||||
ARG PYTHON_VERSION=3.11.5
|
||||
|
||||
FROM python:${PYTHON_VERSION}-slim as base
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG SOURCE_DIR
|
||||
ARG TESTS_DIR
|
||||
|
||||
# Prevents Python from writing pyc files.
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Keeps Python from buffering stdout and stderr to avoid situations where
|
||||
# the application crashes without emitting any logs due to buffering.
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
COPY $SOURCE_DIR /app/$SOURCE_DIR
|
||||
|
||||
WORKDIR /app/$SOURCE_DIR
|
||||
RUN chmod -R 755 /app/$SOURCE_DIR
|
||||
|
||||
COPY $SOURCE_DIR/run-tests.sh /usr/local/bin/run-tests.sh
|
||||
RUN chmod +x /usr/local/bin/run-tests.sh
|
||||
|
||||
# Download dependencies as a separate step to take advantage of Docker's caching.
|
||||
# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
|
||||
# Leverage a bind mount to requirements.txt to avoid having to copy them into
|
||||
# into this layer.
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
--mount=type=bind,source=${SOURCE_DIR}/requirements.txt,target=requirements.txt \
|
||||
python -m pip install -r requirements.txt
|
||||
|
||||
# RUN --mount=type=cache,target=/root/.cache/node_modules \
|
||||
# --mount=type=bind,source=package.json,target=package.json \
|
||||
# npm install
|
||||
|
||||
WORKDIR $TESTS_DIR
|
||||
ENTRYPOINT ["run-tests.sh"]
|
||||
CMD [""]
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# For examples, see the Awesome Compose repository:
|
||||
# https://github.com/docker/awesome-compose
|
||||
services:
|
||||
test:
|
||||
image: docs-v2-tests
|
||||
env_file:
|
||||
- ./test/.env.influxdbv3
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./test/tmp
|
||||
target: /app/test/tmp
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- SOURCE_DIR=test
|
||||
- TESTS_DIR=tmp
|
||||
|
|
@ -44,6 +44,8 @@ whichever threshold is met first.
|
|||
Before writing data points to InfluxDB, sort tags by key in lexicographic order.
|
||||
_Verify sort results match results from the [Go `bytes.Compare` function](http://golang.org/pkg/bytes/#Compare)._
|
||||
|
||||
<!--pytest.mark.skip-->
|
||||
|
||||
```sh
|
||||
# Line protocol example with unsorted tags
|
||||
measurement,tagC=therefore,tagE=am,tagA=i,tagD=i,tagB=think fieldKey=fieldValue 1562020262
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
#! /bin/bash
|
||||
|
||||
# Path: test.sh
|
||||
# Description:
|
||||
# This script is used to copy content files for testing and to run tests on tests on those temporary copies.
|
||||
# The temporary files are shared between the host and the Docker container
|
||||
# using a bind mount configured in compose.yaml.
|
||||
#
|
||||
# Docker compose now has an experimental file watch feature
|
||||
# (https://docs.docker.com/compose/file-watch/) that is likely preferable to the
|
||||
# strategy here.
|
||||
#
|
||||
# Usage:
|
||||
# The default behavior is to test all *.md files that have been added or modified in the current branch, effectively:
|
||||
#
|
||||
# `git diff --name-only --diff-filter=AM --relative master | grep -E '\.md$' | ./test.sh`
|
||||
#
|
||||
# To specify files to test, in your terminal command line, pass a file pattern as the only argument to the script--for example:
|
||||
#
|
||||
# sh test.sh ./content/**/*.md
|
||||
##
|
||||
|
||||
paths="$1"
|
||||
target=./test/tmp
|
||||
testrun=./test/.test-run.txt
|
||||
mkdir -p "$target"
|
||||
cat /dev/null > "$testrun"
|
||||
rm -rf "$target"/*
|
||||
|
||||
# Check if the user provided a path to copy.
|
||||
if [ -z "$paths" ]; then
|
||||
echo "No path provided. Running tests for *.md files that have been added or modified in the current branch."
|
||||
paths=$(git diff --name-only --diff-filter=AM --relative master | \
|
||||
grep -E '\.md$')
|
||||
else
|
||||
paths=$(find "$paths" -type f -name '*.md')
|
||||
fi
|
||||
|
||||
# Log the list of files to be tested and copy them to the test directory.
|
||||
echo "$paths" >> "$testrun"
|
||||
echo "$paths" | rsync -arv --files-from=- . "$target"
|
||||
# Start a new container and run the tests.
|
||||
docker compose run --no-TTY test
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# Include any files or directories that you don't want to be copied to your
|
||||
# container here (e.g., local build artifacts, temporary files, etc.).
|
||||
#
|
||||
# For more help, visit the .dockerignore file reference guide at
|
||||
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||
|
||||
**/.DS_Store
|
||||
**/__pycache__
|
||||
**/.venv
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.env.influxdbv3
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/target
|
||||
/Cargo.lock
|
||||
node_modules
|
||||
.env*
|
||||
.pytest_cache
|
||||
.test-run.txt
|
||||
tmp
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
Test code blocks in Markdown files.
|
||||
|
||||
This project contains the following:
|
||||
|
||||
- `test.sh`: The primary entrypoint for running tests.
|
||||
Copies Markdown files to a temporary directory shared with the `test` Docker image and runs the test container.
|
||||
- `test/run-tests.sh`: The Docker image entrypoint.
|
||||
Substitutes placeholders with environment variables in code blocks.
|
||||
Passes test files to test runners (for example, `pytest --codeblocks` for Python and shell code samples).
|
||||
- `compose.yaml` and `Dockerfile`: Docker image for the **test** service that installs test dependencies and passes test files to test runners.
|
||||
|
||||
## Set configuration values
|
||||
|
||||
To set your InfluxDB credentials for testing, create the file `.env.influxdbv3` and add key=value properties for the following:
|
||||
|
||||
```text
|
||||
INFLUX_HOST=https://us-east-1-1.aws.cloud2.influxdata.com
|
||||
INFLUX_TOKEN=3S3SFrpFbnNR_pZ3Cr6LMN...==
|
||||
INFLUX_ORG=28d1f2f.........
|
||||
INFLUX_DATABASE=get-started
|
||||
```
|
||||
|
||||
Storing configuration properties in a `.env` (dotenv) file is generally preferable to using environment variables.
|
||||
|
||||
## Build the image
|
||||
|
||||
1. Install Docker for your system.
|
||||
|
||||
2. Build the Docker image.
|
||||
|
||||
```shell
|
||||
docker compose build test
|
||||
```
|
||||
|
||||
After editing configuration or files used by the image, re-run the preceding build command.
|
||||
|
||||
## Run tests
|
||||
|
||||
Test code blocks in Markdown files that have changed relative to your git `master` branch:
|
||||
|
||||
```sh
|
||||
sh test.sh
|
||||
```
|
||||
|
||||
Test code blocks in files that match a pattern:
|
||||
|
||||
```sh
|
||||
sh test.sh ./content/**/*.md
|
||||
```
|
||||
|
||||
`test.sh` copies files into `./test/tmp/` for testing and runs the tests in Docker.
|
||||
|
||||
### Test runners
|
||||
|
||||
_Experimental--work in progress_
|
||||
|
||||
[pytest-codeblocks](https://github.com/nschloe/pytest-codeblocks/tree/main) extracts code from python and shell Markdown code blocks and executes assertions for the code.
|
||||
If you don't assert a value, `--codeblocks` considers a non-zero exit code to be a failure.
|
||||
_Note_: `pytest --codeblocks` uses Python's `subprocess.run()` to execute shell code.
|
||||
|
||||
To assert (and display) the expected output of your code, follow the code block with the `<!--pytest-codeblocks:expected-output-->` comment tag, and then the expected output in a code block--for example:
|
||||
|
||||
```python
|
||||
print("Hello, world!")
|
||||
```
|
||||
|
||||
<!--pytest-codeblocks:expected-output-->
|
||||
|
||||
If successful, the output is the following:
|
||||
|
||||
```
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
pytest-codeblocks has features for skipping tests and marking blocks as failed.
|
||||
To learn more, see the pytest-codeblocks README and tests.
|
||||
|
||||
#### Other tools and ideas for testing code blocks
|
||||
|
||||
The `codedown` NPM package extracts code from Markdown code blocks for each language and
|
||||
can pipe the output to a test runner for the language.
|
||||
|
||||
`pytest` and `pytest-codeblocks` use the Python `Assertions` module to keep testing overhead low.
|
||||
Node.js also provides an `Assertions` package.
|
||||
|
||||
The `runmd` NPM package runs `javascript` code blocks in Markdown and generates a new Markdown file with the code block output inserted.
|
||||
|
||||
## Troubleshoot
|
||||
|
||||
### Pytest collected 0 items
|
||||
|
||||
Potential reasons:
|
||||
|
||||
- See the test discovery options in `pytest.ini`.
|
||||
- For Python code blocks, use the following delimiter:
|
||||
|
||||
```python
|
||||
# Codeblocks runs this block.
|
||||
```
|
||||
|
||||
`pytest --codeblocks` ignores code blocks that use the following:
|
||||
|
||||
```py
|
||||
# Codeblocks ignores this block.
|
||||
```
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "docs-codesamples",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"example": "examples"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"runmd": "^1.3.9"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# pytest configuration file
|
||||
[pytest]
|
||||
# Collect tests.
|
||||
# Collect tests from files matching file name pattern.
|
||||
python_files = *_test.py *_test_sh.py
|
||||
# Collect classes with names ending in Test.
|
||||
python_classes = *Test
|
||||
# Collect all functions.
|
||||
python_functions = *
|
||||
|
||||
filterwarnings = ignore::pytest.PytestReturnNotNoneWarning
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
## Code sample dependencies
|
||||
influxdb3-python
|
||||
pandas
|
||||
python-dotenv==1.0.0
|
||||
## Test dependencies
|
||||
pytest==7.4.1
|
||||
pytest-cov==2.12.1
|
||||
pytest-codeblocks==0.16.1
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
|
||||
for file in "$TEST_DIR"/*; do
|
||||
if [ -f "$file" ]; then
|
||||
# Replace placeholder values with environment variables.
|
||||
sed -i '' 's|https:\/\/{{< influxdb/host >}}|$INFLUX_HOST|g;
|
||||
s/API_TOKEN/$INFLUX_TOKEN/g;
|
||||
s/DATABASE_TOKEN/$INFLUX_TOKEN/g;
|
||||
s/BUCKET_NAME/$INFLUX_DATABASE/g;
|
||||
s/DATABASE_NAME/$INFLUX_DATABASE/g;' \
|
||||
$file
|
||||
fi
|
||||
done
|
||||
|
||||
pytest --codeblocks $TEST_DIR
|
||||
Loading…
Reference in New Issue