Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .cargo/mutants.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Un mutante equivalente no es un hueco de cobertura: es código que se puede
# escribir de dos formas con el mismo comportamiento. Excluirlo es honesto solo
# si está demostrado y si algo avisa cuando deje de serlo.
#
# `of_image` decide escalar con `longest_side > max_side`. El único caso donde
# `>` y `>=` discrepan es cuando el lado mayor vale exactamente `max_side`, y
# ahí el ratio interno de `image::thumbnail` es 1: devuelve la imagen sin tocar
# un solo píxel. Lo prueba `scaling_to_the_size_it_already_has_changes_nothing`,
# que falla si una versión futura de `image` cambia ese comportamiento.


exclude_re = ["replace > with >= in of_image"]

exclude_globs = [
"crates/cp-win/src/capture.rs",
"crates/cp-win/src/paste.rs",
"crates/cp-win/src/restore.rs",
"crates/cp-win/src/watching.rs",
]
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ jobs:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- run: cargo fmt --all -- --check
- name: The probe body is formatted too
run: rustfmt --check --edition 2024 crates/cp-mac/examples/probe/battery.rs
- name: The probe bodies are formatted too
run: rustfmt --check --edition 2024 crates/cp-mac/examples/probe/battery.rs crates/cp-win/examples/probe/battery.rs
- run: cargo clippy --workspace --all-targets -- -D warnings
- run: cargo test --workspace
- run: cargo run -p cp-mac --example probe
if: runner.os == 'macOS'
- name: The probe reads a real clipboard, so leave something on it
if: runner.os == 'Windows'
shell: pwsh
run: Set-Clipboard -Value "cp-probe"
- run: cargo run -p cp-win --example probe
if: runner.os == 'Windows'

Expand Down
20 changes: 3 additions & 17 deletions .github/workflows/cla.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
name: CLA

# Dual licensing only works if every merged line can be relicensed, so a
# contribution cannot land before its author has signed. Signatures are stored
# in a separate repository rather than this one to keep the audit trail intact
# even if this repo's history is ever rewritten.
#
# Tools are deliberately NOT on the allowlist. A co-author that cannot sign
# blocks its own pull request, and that is the intended outcome: CONTRIBUTING
# asks for the credit line to name people only, so a blocked PR means the guide
# went unread. Reopening it without that trailer costs a minute.

on:
issue_comment:
types: [created]
Expand All @@ -26,8 +16,7 @@ jobs:
cla:
name: Check CLA signature
runs-on: ubuntu-latest
# Only react to the signature phrase or to PR events, never to ordinary
# comment traffic.

if: >
(github.event_name == 'issue_comment' &&
github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') ||
Expand All @@ -37,13 +26,10 @@ jobs:
uses: contributor-assistant/github-action@v2.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Fine-grained PAT with contents:write on the signatures repository.
# Without it the action cannot record a signature.

PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_SIGNATURES_TOKEN }}
with:
# Scoped per project: one signatures repository serves every
# dual-licensed repo, but a signature given for one CLA must never
# count as consent for another project's.

path-to-signatures: "signatures/copypaste/v1/cla.json"
path-to-document: "https://github.com/rgdevment/CopyPaste/blob/main/CLA.md"
branch: "main"
Expand Down
128 changes: 128 additions & 0 deletions .github/workflows/mutants-sweep.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Mutants sweep

on:
schedule:
- cron: "0 4 * * 0"
workflow_dispatch:

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
core:
name: cp-core and cp-store / shard ${{ matrix.shard }} of 2
runs-on: macos-15
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
shard: [1, 2]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@cargo-mutants

- name: Half of every mutant the pure crates carry
id: run
run: |
set +e
cargo mutants -p cp-core -p cp-store --shard $(( ${{ matrix.shard }} - 1 ))/2 --no-times -j 2
echo "code=$?" >> "$GITHUB_OUTPUT"

- name: What survived
if: always()
env:
CODE: ${{ steps.run.outputs.code }}
TITLE: cp-core and cp-store, shard ${{ matrix.shard }} of 2
run: bash scripts/mutants-report.sh

- uses: actions/upload-artifact@v4
if: always()
with:
name: mutants-core-${{ matrix.shard }}
path: mutants.out/
retention-days: 14

windows:
name: cp-win / shard ${{ matrix.shard }} of 2
runs-on: windows-2025
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
shard: [1, 2]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@cargo-mutants

- name: Half of every mutant the Windows crate carries
id: run
shell: bash
run: |
set +e
cargo mutants -p cp-win --shard $(( ${{ matrix.shard }} - 1 ))/2 --no-times -j 2
echo "code=$?" >> "$GITHUB_OUTPUT"

- name: What survived
if: always()
shell: bash
env:
CODE: ${{ steps.run.outputs.code }}
TITLE: cp-win, shard ${{ matrix.shard }} of 2
run: bash scripts/mutants-report.sh

- uses: actions/upload-artifact@v4
if: always()
with:
name: mutants-windows-${{ matrix.shard }}
path: mutants.out/
retention-days: 14

score:
name: what the whole sweep came to
if: always() && !cancelled()
needs: [core, windows]
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- uses: actions/download-artifact@v4
with:
pattern: mutants-*
path: outcomes

- name: Every shard told as one number
env:
WANT: 4
run: python3 scripts/mutants_score.py

- name: The number, on the branch that holds nothing else
run: |
mv mutants.json "$RUNNER_TEMP/mutants.json"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

if git fetch origin score --depth 1 2>/dev/null; then
git switch -C score FETCH_HEAD
else
git switch --orphan score
fi
git rm -rq --cached . 2>/dev/null || true

cp "$RUNNER_TEMP/mutants.json" mutants.json
git add -- mutants.json
if git diff --cached --quiet; then
echo "the score has not moved"
exit 0
fi
git commit -m "chore: what the sweep of $(date -u +%Y-%m-%d) came to"
git push origin score
75 changes: 75 additions & 0 deletions .github/workflows/mutants.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Mutants

on:
pull_request:
paths:
- crates/**
- .cargo/mutants.toml
- .github/workflows/mutants.yml

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
on-the-branch:
name: ${{ matrix.name }} / the lines this branch changed
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- os: windows-2025
name: windows
crates: -p cp-core -p cp-store -p cp-win
- os: macos-15
name: macos
crates: -p cp-core -p cp-store
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: What the branch changed in the crates
id: touched
shell: bash
env:
BASE: ${{ github.event.pull_request.base.sha }}
run: |
from=$(git merge-base "$BASE" HEAD)
git diff "$from" -- crates > branch.diff
if [ -s branch.diff ]; then echo "any=yes" >> "$GITHUB_OUTPUT"; fi

- uses: dtolnay/rust-toolchain@stable
if: steps.touched.outputs.any == 'yes'
- uses: Swatinem/rust-cache@v2
if: steps.touched.outputs.any == 'yes'
- uses: taiki-e/install-action@cargo-mutants
if: steps.touched.outputs.any == 'yes'

- name: A mutant that lives on a line this branch wrote
id: run
if: steps.touched.outputs.any == 'yes'
shell: bash
run: |
set +e
cargo mutants ${{ matrix.crates }} --in-diff branch.diff --no-times -j 2
echo "code=$?" >> "$GITHUB_OUTPUT"

- name: What survived
if: always() && steps.touched.outputs.any == 'yes'
shell: bash
env:
CODE: ${{ steps.run.outputs.code }}
TITLE: ${{ matrix.name }}, the lines this branch changed
run: bash scripts/mutants-report.sh

- uses: actions/upload-artifact@v4
if: always() && steps.touched.outputs.any == 'yes'
with:
name: mutants-branch-${{ matrix.name }}
path: mutants.out/
retention-days: 7
40 changes: 9 additions & 31 deletions .github/workflows/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,25 @@ jobs:
- name: No voseo in the Spanish anywhere
run: |
if grep -rniE '\b(vos|tenés|querés|podés|andá|mirá|hacé|che)\b' \
crates docs README.md --include='*.rs' --include='*.md' 2>/dev/null; then
crates docs README.md --include='*.rs' --include='*.md' 2>/dev/null | grep -q .; then
echo "espanol neutro, sin voseo"; exit 1
fi

- name: No peninsular Spanish in what a person reads
run: |
if grep -rniE '\b(fichero|ficheros|ordenador|pulsa|pulsar|pulsando)\b' \
crates docs README.md --include='*.rs' --include='*.md' 2>/dev/null; then
crates docs README.md --include='*.rs' --include='*.md' 2>/dev/null | grep -q .; then
echo "espanol neutro: archivo, computador, presiona"; exit 1
fi

- name: The code carries no comments but SAFETY
- name: The code carries no comments
run: |
if grep -rnE '^[ ]*//' crates --include='*.rs' | grep -v 'SAFETY:'; then
if grep -rnE '[/]{2}' crates --include='*.rs' | grep -vE '[a-z]+:[/][/]' | grep -q .; then
echo "el codigo va sin comentarios; lo que haya que explicar va al expediente"; exit 1
fi

- name: Every SAFETY note is one line of English
run: |
if ! grep -rh 'SAFETY:' crates --include='*.rs' | iconv -f utf-8 -t ascii >/dev/null; then
echo "las notas SAFETY van en ingles"; exit 1
if grep -rnE '[/][*]' crates --include='*.rs' | grep -q .; then
echo "tampoco comentarios de bloque"; exit 1
fi
awk '/SAFETY:/ { safety = 1; next }
/^[ ]*\/\// { if (safety) { print FILENAME":"FNR": la nota SAFETY sigue en otra linea"; bad = 1 } }
{ safety = 0 }
END { exit bad }' $(find crates -name '*.rs')

deterministic:
name: Tests are deterministic (${{ matrix.os }})
Expand All @@ -89,24 +82,6 @@ jobs:
- name: Twenty consecutive runs of the core
run: for i in $(seq 1 20); do cargo test -p cp-core --quiet || exit 1; done

mutants:
name: Every mutant dies in the crates without a system call (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-15
crates: -p cp-core -p cp-store
- os: windows-2025
crates: -p cp-core -p cp-store -p cp-win
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@cargo-mutants
- run: cargo mutants ${{ matrix.crates }} --no-times

coverage:
name: Coverage does not slip
runs-on: macos-15
Expand All @@ -132,6 +107,9 @@ jobs:
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@cargo-llvm-cov
- run: cargo llvm-cov --no-report -p cp-core -p cp-store -p cp-win -p cp-win-sys
- name: The probe reads a real clipboard, so leave something on it
shell: pwsh
run: Set-Clipboard -Value "cp-probe"
- run: cargo llvm-cov --no-report run -p cp-win --example probe
- run: cargo llvm-cov report --fail-under-lines 95 --summary-only

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
<a href="https://codecov.io/gh/rgdevment/CopyPaste">
<img src="https://codecov.io/gh/rgdevment/CopyPaste/branch/main/graph/badge.svg?style=flat-square" alt="Coverage"/>
</a>
<a href="https://github.com/rgdevment/CopyPaste/actions/workflows/mutants-sweep.yml">
<img src="https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fraw.githubusercontent.com%2Frgdevment%2FCopyPaste%2Fscore%2Fmutants.json" alt="Mutation score"/>
</a>
<a href="https://github.com/rgdevment/CopyPaste/releases">
<img src="https://img.shields.io/github/v/release/rgdevment/CopyPaste?include_prereleases&style=flat-square&label=Latest&color=0078D4" alt="Latest Release"/>
</a>
Expand Down
12 changes: 4 additions & 8 deletions crates/cp-core/src/dib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,10 +388,8 @@ mod tests {
fn a_mixed_alpha_channel_is_real_transparency() {
let mut dib = Dib::rgb32(2, 2);
dib.pixels = vec![
0x40, 0x80, 0xC0, 0xFF, //
0x40, 0x80, 0xC0, 0x00, //
0x40, 0x80, 0xC0, 0xFF, //
0x40, 0x80, 0xC0, 0xFF,
0x40, 0x80, 0xC0, 0xFF, 0x40, 0x80, 0xC0, 0x00, 0x40, 0x80, 0xC0, 0xFF, 0x40, 0x80,
0xC0, 0xFF,
];
assert_eq!(
alpha(&dib.build()),
Expand Down Expand Up @@ -643,10 +641,8 @@ mod tests {
dib.compression = BI_BITFIELDS;
dib.header_size = 124;
dib.pixels = vec![
0x20, 0x60, 0xA0, 0x00, //
0x20, 0x60, 0xA0, 0x80, //
0x20, 0x60, 0xA0, 0xC0, //
0x20, 0x60, 0xA0, 0xFF,
0x20, 0x60, 0xA0, 0x00, 0x20, 0x60, 0xA0, 0x80, 0x20, 0x60, 0xA0, 0xC0, 0x20, 0x60,
0xA0, 0xFF,
];
let mut raw = dib.build();
raw[40..44].copy_from_slice(&0x00FF_0000u32.to_le_bytes());
Expand Down
Loading
Loading