Skip to content

Commit

Permalink
Merge pull request #156 from tomato42/cosmic-ray
Browse files Browse the repository at this point in the history
Add mutation testing to CI
  • Loading branch information
tomato42 authored Jan 15, 2024
2 parents a4a8acd + 02c8350 commit 0d5a38c
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 46 deletions.
65 changes: 51 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,10 @@ jobs:
if: ${{ contains(matrix.tox-env, 'gmpyp') }}
run: pip install gmpy
- name: Install gmpy2 dependencies
if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') }}
if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') || matrix.mutation == 'true' }}
run: sudo apt-get install -y libmpfr-dev libmpc-dev
- name: Install gmpy2
if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') }}
if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') || matrix.mutation == 'true' }}
run: pip install gmpy2
- name: Install build dependencies (2.6)
if: ${{ matrix.python-version == '2.6' }}
Expand Down Expand Up @@ -268,7 +268,8 @@ jobs:
- name: Install mutation testing dependencies
if: ${{ matrix.mutation == 'true' }}
run: |
pip install cosmic-ray
pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip
pip install pytest-timeout
- name: Display installed python package versions
run: pip list
- name: Test native speed
Expand Down Expand Up @@ -296,19 +297,40 @@ jobs:
cosmic-ray init cosmic-ray.toml session-vs-master.sqlite
git branch master origin/master
cr-filter-git --config cosmic-ray.toml session-vs-master.sqlite
cr-report session-vs-master.sqlite | tail -n 5
cr-report session-vs-master.sqlite | tail -n 3
- name: Exec mutation testing for PR
if: ${{ matrix.mutation == 'true' && github.event.pull_request }}
run: |
cosmic-ray exec cosmic-ray.toml session-vs-master.sqlite
systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G cosmic-ray --verbosity INFO exec cosmic-ray.toml session-vs-master.sqlite &
cosmic_pid=$!
for i in $(seq 1 600); do
# wait for test execution at most 10 minutes
kill -s 0 $cosmic_pid || break
sleep 1
done
kill $cosmic_pid || true
wait $cosmic_pid || true
- name: Check test coverage for PR
if: ${{ matrix.mutation == 'true' && github.event.pull_request }}
run: |
# remove not-executed results
sqlite3 session-vs-master.sqlite "DELETE from work_results WHERE work_results.worker_outcome = 'SKIPPED'"
cr-report session-vs-master.sqlite | tail -n 5
# check if executed have at most 5% survival rate
cr-rate --fail-over 5 session-vs-master.sqlite
cr-report session-vs-master.sqlite | tail -n 3
- name: Generate html report
if: ${{ matrix.mutation == 'true' && github.event.pull_request }}
run: |
cr-html session-vs-master.sqlite > cosmic-ray.html
- name: Archive mutation testing results
if: ${{ matrix.mutation == 'true' && github.event.pull_request }}
uses: actions/upload-artifact@v3
with:
name: mutation-PR-coverage-report
path: cosmic-ray.html
- name: Check test coverage for PR
if: ${{ matrix.mutation == 'true' && github.event.pull_request }}
run: |
# check if executed have at most 50% survival rate
cr-rate --estimate --confidence 99.9 --fail-over 50 session-vs-master.sqlite
- name: instrumental test coverage on PR
if: ${{ contains(matrix.opt-deps, 'instrumental') && github.event.pull_request }}
env:
Expand Down Expand Up @@ -372,6 +394,9 @@ jobs:
mutation-prepare:
name: Prepare job files for the mutation runners
# use runner minutes on mutation testing only after the PR passed basic
# testing
needs: coveralls
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Expand All @@ -386,7 +411,8 @@ jobs:
key: sessions-${{ github.sha }}
- name: Install cosmic-ray
run: |
pip3 install cosmic-ray
pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip
pip install pytest-timeout
- name: Install dependencies
run: |
sudo apt-get install -y sqlite3
Expand Down Expand Up @@ -461,7 +487,8 @@ jobs:
- name: Install build dependencies
run: |
pip install -r build-requirements.txt
pip install cosmic-ray
pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip
pip install pytest-timeout
- name: Run mutation testing
run: |
cp sessions/session-${{ matrix.name }}.sqlite session.sqlite
Expand Down Expand Up @@ -608,7 +635,8 @@ jobs:
key: sessions-${{ github.sha }}-19-done
- name: Install cosmic-ray
run: |
pip3 install cosmic-ray
pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip
pip install pytest-timeout
- name: Install dependencies
run: |
sudo apt-get install -y sqlite3
Expand All @@ -621,13 +649,20 @@ jobs:
- name: Report executed
run: |
cr-report session.sqlite | tail -n 3
- name: Log survival estimate
run: cr-rate --estimate --fail-over 32 --confidence 99.9 session.sqlite || true
- name: Generate html report
run: |
cr-html session.sqlite > cosmic-ray.html
- name: Archive mutation testing results
uses: actions/upload-artifact@v3
with:
name: mutation-coverage-report
path: cosmic-ray.html
- name: Get mutation score
run: |
echo "print(100-$(cr-rate session.sqlite))" > print-score.py
echo "print('{0:.2f}'.format(100-$(cr-rate session.sqlite)))" > print-score.py
echo "MUT_SCORE=$(python print-score.py)" >> $GITHUB_ENV
- name: Create mutation score badge
if: ${{ !github.event.pull_request }}
uses: schneegans/[email protected]
with:
auth: ${{ secrets.GIST_SECRET }}
Expand All @@ -638,3 +673,5 @@ jobs:
valColorRange: ${{ env.MUT_SCORE }}
maxColorRange: 100
minColorRange: 0
- name: Check survival estimate
run: cr-rate --estimate --fail-over 32 --confidence 99.9 session.sqlite
2 changes: 1 addition & 1 deletion cosmic-ray-12way.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
module-path = "src"
timeout = 20.0
excluded-modules = ['src/ecdsa/_sha3.py', 'src/ecdsa/_version.py', 'src/ecdsa/test*']
test-command = "pytest -x --fast -m 'not slow' src/"
test-command = "pytest --timeout=30 -x --fast -m 'not slow' src/"

[cosmic-ray.distributor]
name = "http"
Expand Down
2 changes: 1 addition & 1 deletion cosmic-ray.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
module-path = "src"
timeout = 20.0
excluded-modules = ['src/ecdsa/_sha3.py', 'src/ecdsa/_version.py', 'src/ecdsa/test*']
test-command = "pytest -x --fast -m 'not slow' src/"
test-command = "pytest --timeout 30 -x --fast -m 'not slow' src/"

[cosmic-ray.distributor]
name = "local"
Expand Down
4 changes: 3 additions & 1 deletion src/ecdsa/ellipticcurve.py
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,9 @@ def double(self):

X3, Y3, Z3, T3 = self._double(X1, Y1, Z1, T1, p, a)

if not X3 or not T3:
# both Ed25519 and Ed448 have prime order, so no point added to
# itself will equal zero
if not X3 or not T3: # pragma: no branch
return INFINITY
return PointEdwards(self.__curve, X3, Y3, Z3, T3, self.__order)

Expand Down
6 changes: 0 additions & 6 deletions src/ecdsa/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1102,12 +1102,6 @@ def from_der(cls, string, hashfunc=sha1, valid_curve_encodings=None):

curve = Curve.from_der(algorithm_identifier, valid_curve_encodings)

if empty != b"":
raise der.UnexpectedDER(
"unexpected data after algorithm identifier: %s"
% binascii.hexlify(empty)
)

# Up next is an octet string containing an ECPrivateKey. Ignore
# the optional "attributes" and "publicKey" fields that come after.
s, _ = der.remove_octet_string(s)
Expand Down
30 changes: 20 additions & 10 deletions src/ecdsa/numbertheory.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
except NameError:
xrange = range
try:
from gmpy2 import powmod
from gmpy2 import powmod, mpz

GMPY2 = True
GMPY = False
except ImportError:
except ImportError: # pragma: no branch
GMPY2 = False
try:
from gmpy import mpz
Expand All @@ -33,8 +33,15 @@
except ImportError:
GMPY = False


if GMPY2 or GMPY: # pragma: no branch
integer_types = tuple(integer_types + (type(mpz(1)),))


import math
import warnings
import random
from .util import bit_length


class Error(Exception):
Expand Down Expand Up @@ -216,14 +223,15 @@ def square_root_mod_prime(a, p):
range_top = min(0x7FFFFFFF, p)
else:
range_top = p
for b in xrange(2, range_top):
for b in xrange(2, range_top): # pragma: no branch
if jacobi(b * b - 4 * a, p) == -1:
f = (a, -b, 1)
ff = polynomial_exp_mod((0, 1), (p + 1) // 2, f, p)
if ff[1]:
raise SquareRootError("p is not prime")
return ff[0]
raise RuntimeError("No b found.")
# just an assertion
raise RuntimeError("No b found.") # pragma: no cover


# because all the inverse_mod code is arch/environment specific, and coveralls
Expand Down Expand Up @@ -346,7 +354,7 @@ def factorization(n):
q, r = divmod(n, d)
if r == 0:
count = 1
while d <= n:
while d <= n: # pragma: no branch
n = q
q, r = divmod(n, d)
if r != 0:
Expand All @@ -370,7 +378,8 @@ def factorization(n):
if r == 0: # d divides n. How many times?
count = 1
n = q
while d <= n: # As long as d might still divide n,
# As long as d might still divide n,
while d <= n: # pragma: no branch
q, r = divmod(n, d) # see if it does.
if r != 0:
break
Expand Down Expand Up @@ -555,16 +564,17 @@ def is_prime(n):
return True
else:
return False

if gcd(n, 2 * 3 * 5 * 7 * 11) != 1:
# 2310 = 2 * 3 * 5 * 7 * 11
if gcd(n, 2310) != 1:
return False

# Choose a number of iterations sufficient to reduce the
# probability of accepting a composite below 2**-80
# (from Menezes et al. Table 4.4):

t = 40
n_bits = 1 + int(math.log(n, 2))
n_bits = 1 + bit_length(n)
assert 11 <= n_bits <= 16384
for k, tt in (
(100, 27),
(150, 18),
Expand All @@ -591,7 +601,7 @@ def is_prime(n):
s = s + 1
r = r // 2
for i in xrange(t):
a = smallprimes[i]
a = random.choice(smallprimes)
y = pow(a, r, n)
if y != 1 and y != n - 1:
j = 1
Expand Down
7 changes: 7 additions & 0 deletions src/ecdsa/test_ecdh.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import sys
import shutil
import subprocess
import pytest
Expand All @@ -16,6 +17,8 @@
NIST384p,
NIST521p,
BRAINPOOLP160r1,
SECP112r2,
SECP128r1,
)
from .curves import curves
from .ecdh import (
Expand All @@ -29,6 +32,10 @@
from .ellipticcurve import CurveEdTw


if "--fast" in sys.argv: # pragma: no cover
curves = [SECP112r2, SECP128r1]


@pytest.mark.parametrize(
"vcurve",
curves,
Expand Down
23 changes: 22 additions & 1 deletion src/ecdsa/test_eddsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,27 @@ def test_ed25519_eq_x_different_y():
assert a != b


def test_ed25519_mul_by_order():
g = PointEdwards(
curve_ed25519,
generator_ed25519.x(),
generator_ed25519.y(),
1,
generator_ed25519.x() * generator_ed25519.y(),
)

assert g * generator_ed25519.order() == INFINITY


def test_radd():

a = PointEdwards(curve_ed25519, 1, 1, 1, 1)

p = INFINITY + a

assert p == a


def test_ed25519_test_normalisation_and_scaling():
x = generator_ed25519.x()
y = generator_ed25519.y()
Expand Down Expand Up @@ -664,7 +685,7 @@ def test_invalid_r_value(self):


HYP_SETTINGS = dict()
if "--fast" in sys.argv:
if "--fast" in sys.argv: # pragma: no cover
HYP_SETTINGS["max_examples"] = 2
else:
HYP_SETTINGS["max_examples"] = 10
Expand Down
31 changes: 31 additions & 0 deletions src/ecdsa/test_ellipticcurve.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,34 @@ def test_inequality_points(self):
def test_inequality_points_diff_types(self):
c = CurveFp(100, -3, 100)
self.assertNotEqual(self.g_23, c)

def test_to_bytes_from_bytes(self):
p = Point(self.c_23, 3, 10)

self.assertEqual(p, Point.from_bytes(self.c_23, p.to_bytes()))

def test_add_to_neg_self(self):
p = Point(self.c_23, 3, 10)

self.assertEqual(INFINITY, p + (-p))

def test_add_to_infinity(self):
p = Point(self.c_23, 3, 10)

self.assertIs(p, p + INFINITY)

def test_mul_infinity_by_scalar(self):
self.assertIs(INFINITY, INFINITY * 10)

def test_mul_by_negative(self):
p = Point(self.c_23, 3, 10)

self.assertEqual(p * -5, (-p) * 5)

def test_str_infinity(self):
self.assertEqual(str(INFINITY), "infinity")

def test_str_point(self):
p = Point(self.c_23, 3, 10)

self.assertEqual(str(p), "(3,10)")
6 changes: 6 additions & 0 deletions src/ecdsa/test_jacobi.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,12 @@ def test_equality_with_wrong_curves(self):

self.assertNotEqual(p_a, p_b)

def test_add_with_point_at_infinity(self):
pj1 = PointJacobi(curve=CurveFp(23, 1, 1, 1), x=2, y=3, z=1, order=1)
x, y, z = pj1._add(2, 3, 1, 5, 5, 0, 23)

self.assertEqual((x, y, z), (2, 3, 1))

def test_pickle(self):
pj = PointJacobi(curve=CurveFp(23, 1, 1, 1), x=2, y=3, z=1, order=1)
self.assertEqual(pickle.loads(pickle.dumps(pj)), pj)
Expand Down
Loading

0 comments on commit 0d5a38c

Please sign in to comment.