Skip to content

Commit

Permalink
v1.4. support for simultanously executing testmon with pytest-xdist a…
Browse files Browse the repository at this point in the history
…nd pytest-cov.
  • Loading branch information
tarpas committed Oct 17, 2022
1 parent 6af65b0 commit 9989d16
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 176 deletions.
6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[metadata]
name = pytest-testmon
version = 1.3.7
version = 1.4.0b1
license = AGPL
author_email = [email protected]
author = Tibor Arpas, Tomas Matlovic, Daniel Hahler, Martin Racak
author = Tibor Arpas, Tomas Matlovic
description = selects tests affected by changed files and methods
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down Expand Up @@ -36,7 +36,7 @@ classifiers =
python_requires = >=3.7, <3.12
install_requires =
pytest>=5,<8
coverage>=5,<7
coverage>=6,<7

packages =
testmon
Expand Down
1 change: 1 addition & 0 deletions testmon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""PYTEST_DONT_REWRITE"""
18 changes: 15 additions & 3 deletions testmon/configure.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import sys
import re

from coverage.tracer import CTracer
try:
from coverage.tracer import CTracer as Tracer
except ImportError:
from coverage.pytracer import PyTracer as Tracer


def _is_dogfooding(coverage_stack):
return coverage_stack


def _is_debugger():
return sys.gettrace() and not isinstance(sys.gettrace(), CTracer)
return sys.gettrace() and not isinstance(sys.gettrace(), Tracer)


def _is_coverage():
return isinstance(sys.gettrace(), CTracer)
return False


def _deactivate_on_xdist(options):
return False
return (
options.get("numprocesses", False)
or options.get("distload", False)
Expand Down Expand Up @@ -54,6 +62,9 @@ def _get_nocollect_reasons(
if options["testmon_nocollect"]:
return [None]

if cov_plugin:
return []

if coverage and not dogfooding:
return ["coverage.py was detected and simultaneous collection is not supported"]

Expand Down Expand Up @@ -141,6 +152,7 @@ def header_collect_select(config, coverage_stack, cov_plugin=None):
options,
debugger=_is_debugger(),
coverage=_is_coverage(),
dogfooding=_is_dogfooding(coverage_stack),
xdist=_deactivate_on_xdist(options),
cov_plugin=cov_plugin,
)
117 changes: 64 additions & 53 deletions testmon/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
import sqlite3

from collections import namedtuple
from functools import lru_cache

from testmon.process_code import (
blob_to_checksums,
checksums_to_blob,
Fingerprint,
Fingerprints,
)
from testmon.process_code import blob_to_checksums, checksums_to_blob

DATA_VERSION = 0

Expand All @@ -18,12 +14,26 @@
)


class CachedProperty:
def __init__(self, func):
self.__doc__ = getattr(func, "__doc__")
self.func = func

def __get__(self, obj, cls):
if obj is None:
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value


class TestmonDbException(Exception):
pass


def connect(datafile):
connection = sqlite3.connect(datafile)
def connect(datafile, readonly=False):
connection = sqlite3.connect(
f"file:{datafile}{'?mode=ro' if readonly else''}", uri=True
)

connection.execute("PRAGMA synchronous = OFF")
connection.execute("PRAGMA foreign_keys = TRUE ")
Expand Down Expand Up @@ -69,7 +79,7 @@ def update_mtimes(self, new_mtimes):
"UPDATE fingerprint SET mtime=?, checksum=? WHERE id = ?", new_mtimes
)

def remove_unused_fingerprints(self):
def remove_unused_file_fps(self):
with self.con as con:
con.execute(
"""
Expand All @@ -80,7 +90,7 @@ def remove_unused_fingerprints(self):
"""
)

def fetch_or_create_fingerprint(self, filename, mtime, checksum, method_checksums):
def fetch_or_create_file_fp(self, filename, mtime, checksum, method_checksums):
cursor = self.con.cursor()
try:
cursor.execute(
Expand Down Expand Up @@ -112,39 +122,41 @@ def fetch_or_create_fingerprint(self, filename, mtime, checksum, method_checksum
self.update_mtimes([(mtime, checksum, fingerprint_id)])
return fingerprint_id

def insert_node_fingerprints(
self, nodeid, fingerprints, failed=False, duration=None
):
def insert_node_file_fps(self, nodes_fingerprints, fa_durs=None):
if fa_durs is None:
fa_durs = {}
with self.con as con:
cursor = con.cursor()
cursor.execute(
"""
INSERT OR REPLACE INTO node
(environment, name, duration, failed)
VALUES (?, ?, ?, ?)
""",
(
self.env,
nodeid,
duration,
1 if failed else 0,
),
)
node_id = cursor.lastrowid

# record: Fingerprint
for record in fingerprints:
fingerprint_id = self.fetch_or_create_fingerprint(
record["filename"],
record["mtime"],
record["checksum"],
checksums_to_blob(record["method_checksums"]),
)

for nodeid in nodes_fingerprints:
fingerprints = nodes_fingerprints[nodeid]
failed, duration = fa_durs.get(nodeid, (0, None))
cursor.execute(
"INSERT INTO node_fingerprint VALUES (?, ?)",
(node_id, fingerprint_id),
"""
INSERT OR REPLACE INTO node
(environment, name, duration, failed)
VALUES (?, ?, ?, ?)
""",
(
self.env,
nodeid,
duration,
1 if failed else 0,
),
)
node_id = cursor.lastrowid

for record in fingerprints:
fingerprint_id = self.fetch_or_create_file_fp(
record["filename"],
record["mtime"],
record["checksum"],
checksums_to_blob(record["method_checksums"]),
)

cursor.execute(
"INSERT INTO node_fingerprint VALUES (?, ?)",
(node_id, fingerprint_id),
)

def _fetch_data_version(self):
con = self.con
Expand Down Expand Up @@ -172,34 +184,32 @@ def _fetch_attribute(self, attribute, default=None, environment=None):
def init_tables(self):
connection = self.con

connection.execute("CREATE TABLE metadata (dataid TEXT PRIMARY KEY, data TEXT)")

connection.execute(
connection.executescript(
"""
CREATE TABLE metadata (dataid TEXT PRIMARY KEY, data TEXT);
CREATE TABLE environment (
id INTEGER PRIMARY KEY ASC,
name TEXT,
libraries TEXT
);
CREATE TABLE node (
id INTEGER PRIMARY KEY ASC,
environment TEXT,
name TEXT,
duration FLOAT,
failed BIT,
UNIQUE (environment, name)
)
"""
)
);
connection.execute(
"""
CREATE TABLE node_fingerprint (
node_id INTEGER,
fingerprint_id INTEGER,
FOREIGN KEY(node_id) REFERENCES node(id) ON DELETE CASCADE,
FOREIGN KEY(fingerprint_id) REFERENCES fingerprint(id)
)
"""
)
);
connection.execute(
"""
CREATE table fingerprint
(
id INTEGER PRIMARY KEY,
Expand All @@ -208,7 +218,7 @@ def init_tables(self):
mtime FLOAT,
checksum TEXT,
UNIQUE (filename, method_checksums)
)
);
"""
)

Expand Down Expand Up @@ -278,6 +288,7 @@ def all_nodes(self):
)
}

@lru_cache(128)
def filenames_fingerprints(self):
cursor = self.con.execute(
"""
Expand Down
18 changes: 1 addition & 17 deletions testmon/process_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,6 @@
from coverage.misc import NoSource


try:
from typing import TypedDict, List

class Fingerprint(TypedDict):
# filename: str
mtime = None
checksum = None
method_checksums = None
fingerprint_id = None

except ImportError:
Fingerprint = dict

Fingerprints = [Fingerprint]


CHECKUMS_ARRAY_TYPE = "i"


Expand Down Expand Up @@ -135,7 +119,7 @@ def dump_and_block(self, node, end, name="unknown", into_block=False):
fields = []
for field_name, field_value in ast.iter_fields(node):
transform_into_block = (
class_name in ("FunctionDef", "Module")
class_name in ("AsyncFunctionDef", "FunctionDef", "Module")
) and field_name == "body"
fields.append(
(
Expand Down
Loading

0 comments on commit 9989d16

Please sign in to comment.