Skip to content

Commit

Permalink
Added --code flag for collecting code context.
Browse files Browse the repository at this point in the history
  • Loading branch information
rnemes committed Dec 4, 2024
1 parent c3e5747 commit c01e1b5
Show file tree
Hide file tree
Showing 15 changed files with 187 additions and 32 deletions.
1 change: 1 addition & 0 deletions doc/en/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ Command line

--label LABEL Label the test report with the given name, useful to categorize or classify similar reports (aka "run-id").
--driver-info Display drivers startup and teardown information, and visualise driver connections in the report.
--code Collects file path, line number and code context of the assertions.


Highlighted features
Expand Down
2 changes: 2 additions & 0 deletions doc/newsfragments/2981_new.code_context.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added ``--code`` flag to collect code context for the assertions. Code context one-liner will be displayed on the web UI if enabled.
Note that file path information is no longer collected by default. To collect file path information, enable code context.
6 changes: 6 additions & 0 deletions testplan/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ class Testplan(entity.RunnableManager):
categorize or classify similar reports .
:param driver_info: Display driver setup / teardown time and driver
interconnection information in UI report.
:param collect_code_context: Collects the file path, line number and code
context of the assertions.
"""

CONFIG = TestplanConfig
Expand Down Expand Up @@ -194,6 +196,7 @@ def __init__(
extra_deps: Optional[List[Union[str, ModuleType]]] = None,
label: Optional[str] = None,
driver_info: bool = False,
collect_code_context: bool = False,
auto_part_runtime_limit: int = defaults.AUTO_PART_RUNTIME_LIMIT,
plan_runtime_target: int = defaults.PLAN_RUNTIME_TARGET,
**options,
Expand Down Expand Up @@ -256,6 +259,7 @@ def __init__(
extra_deps=extra_deps,
label=label,
driver_info=driver_info,
collect_code_context=collect_code_context,
auto_part_runtime_limit=auto_part_runtime_limit,
plan_runtime_target=plan_runtime_target,
**options,
Expand Down Expand Up @@ -401,6 +405,7 @@ def main_wrapper(
extra_deps=None,
label=None,
driver_info=False,
collect_code_context=False,
auto_part_runtime_limit=defaults.AUTO_PART_RUNTIME_LIMIT,
plan_runtime_target=defaults.PLAN_RUNTIME_TARGET,
**options,
Expand Down Expand Up @@ -462,6 +467,7 @@ def test_plan_inner_inner():
extra_deps=extra_deps,
label=label,
driver_info=driver_info,
collect_code_context=collect_code_context,
auto_part_runtime_limit=auto_part_runtime_limit,
plan_runtime_target=plan_runtime_target,
**options,
Expand Down
8 changes: 8 additions & 0 deletions testplan/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,14 @@ def generate_parser(self) -> HelpParser:
help="Display drivers setup / teardown timing and interconnection information in UI report.",
)

report_group.add_argument(
"--code",
dest="collect_code_context",
action="store_true",
default=self._default_options["collect_code_context"],
help="Collects file path, line number and code context of the assertions.",
)

self.add_arguments(parser)
return parser

Expand Down
1 change: 1 addition & 0 deletions testplan/runnable/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ def get_options(cls):
"skip_strategy", default=common.SkipStrategy.noop()
): Use(common.SkipStrategy.from_option_or_none),
ConfigOption("driver_info", default=False): bool,
ConfigOption("collect_code_context", default=False): bool,
}


Expand Down
8 changes: 8 additions & 0 deletions testplan/testing/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,14 @@ def driver_info(self) -> bool:
return False
return self.cfg.driver_info

@property
def collect_code_context(self) -> bool:
"""
Collecting the file path, line number and code context of the assertions
if enabled.
"""
return getattr(self.cfg, "collect_code_context", False)


class ProcessRunnerTestConfig(TestConfig):
"""
Expand Down
5 changes: 4 additions & 1 deletion testplan/testing/multitest/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
)
from testplan.testing.multitest import suite as mtest_suite
from testplan.testing.multitest.entries import base as entries_base
from testplan.testing.result import report_target
from testplan.testing.result import report_target, collect_code_context
from testplan.testing.multitest.suite import (
get_suite_metadata,
get_testcase_metadata,
Expand Down Expand Up @@ -1158,6 +1158,9 @@ def _run_testcase(
),
)

if self.cfg.collect_code_context:
testcase = collect_code_context(func=testcase)

# specially handle skipped testcases
if hasattr(testcase, "__should_skip__"):
with compose_contexts(
Expand Down
1 change: 1 addition & 0 deletions testplan/testing/multitest/entries/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def __init__(self, description, category=None, flag=None):
# Will be set explicitly via containers
self.line_no = None
self.file_path = None
self.code_context = None

def __str__(self):
return repr(self)
Expand Down
1 change: 1 addition & 0 deletions testplan/testing/multitest/entries/schemas/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class BaseSchema(Schema):
category = fields.String()
flag = fields.String()
file_path = fields.String()
code_context = fields.String()
custom_style = fields.Dict(keys=fields.String(), values=fields.String())

def load(self, *args, **kwargs):
Expand Down
50 changes: 31 additions & 19 deletions testplan/testing/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ def __exit__(self, exc_type, exc_value, tb):
assertion_state = threading.local()


def collect_code_context(func: Callable) -> Callable:
def wrapper(*args, **kwargs):
assertion_state.collect_code_context = True
func(*args, **kwargs)

return wrapper


def report_target(func: Callable, ref_func: Callable = None) -> Callable:
"""
Sets the decorated function's filepath and line-range in assertion state.
Expand Down Expand Up @@ -165,7 +173,10 @@ def wrapper(result, *args, **kwargs):
custom_style = kwargs.pop("custom_style", None)
dryrun = kwargs.pop("dryrun", False)
entry = func(result, *args, **kwargs)
if top_assertion:
if not top_assertion:
return entry

if getattr(assertion_state, "collect_code_context", False):
with MOD_LOCK:
call_stack = inspect.stack()
try:
Expand All @@ -183,34 +194,35 @@ def wrapper(result, *args, **kwargs):
frame = call_stack[1]
entry.file_path = os.path.abspath(frame.filename)
entry.line_no = frame.lineno
entry.code_context = frame.code_context[0].strip()
finally:
# https://docs.python.org/3/library/inspect.html
del frame
del call_stack

if custom_style is not None:
if not isinstance(custom_style, dict):
raise TypeError(
"Use `dict[str, str]` to specify custom CSS style"
)
entry.custom_style = custom_style
if custom_style is not None:
if not isinstance(custom_style, dict):
raise TypeError(
"Use `dict[str, str]` to specify custom CSS style"
)
entry.custom_style = custom_style

assert isinstance(result, AssertionNamespace) or isinstance(
result, Result
), "Incorrect usage of assertion decorator"
assert isinstance(result, AssertionNamespace) or isinstance(
result, Result
), "Incorrect usage of assertion decorator"

if isinstance(result, AssertionNamespace):
result = result.result
if isinstance(result, AssertionNamespace):
result = result.result

if not dryrun:
result.entries.append(entry)
if not dryrun:
result.entries.append(entry)

stdout_registry.log_entry(
entry=entry, stdout_style=result.stdout_style
)
stdout_registry.log_entry(
entry=entry, stdout_style=result.stdout_style
)

if not entry and not result.continue_on_failure:
raise AssertionError(entry)
if not entry and not result.continue_on_failure:
raise AssertionError(entry)

return entry
finally:
Expand Down
102 changes: 95 additions & 7 deletions testplan/web_ui/testing/src/AssertionPane/AssertionHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import Button from "@material-ui/core/Button";
import Linkify from "linkify-react";
import { getWorkspacePath } from "../Common/utils";

library.add(faLayerGroup);

Expand All @@ -35,6 +36,7 @@ function AssertionHeader({
const [isUTCTooltipOpen, setIsUTCTooltipOpen] = useState(false);
const [isPathTooltipOpen, setIsPathTooltipOpen] = useState(false);
const [isDurationTooltipOpen, setIsDurationTooltipOpen] = useState(false);
const [is1LinerTooltipOpen, setIs1LinerTooltipOpen] = useState(false);

/**
* Toggle the visibility of tooltip of file path.
Expand All @@ -57,6 +59,13 @@ function AssertionHeader({
setIsDurationTooltipOpen(!isDurationTooltipOpen);
};

/**
* Toggle the visibility of tooltip of duration between assertions.
*/
const toggle1LinerTooltip = () => {
setIs1LinerTooltipOpen(!is1LinerTooltipOpen);
};

const cardHeaderColorStyle =
assertion.passed === undefined
? styles.cardHeaderColorLog
Expand Down Expand Up @@ -131,15 +140,13 @@ function AssertionHeader({
</>
);

let pathButton = displayPath ? (
const pathButton = assertion.file_path && assertion.line_no ? (
<>
<Button
size="small"
className={css(styles.cardHeaderAlignRight, styles.timeInfo)}
className={css(styles.pathButton)}
onClick={() => {
navigator.clipboard.writeText(getPath(assertion));
}}
style={{ order: 6, marginLeft: "10px" }}
>
<span
id={`tooltip_path_${uid}`}
Expand All @@ -152,14 +159,56 @@ function AssertionHeader({
isOpen={isPathTooltipOpen}
target={`tooltip_path_${uid}`}
toggle={togglePathTooltip}
style={{maxWidth: "400px"}}
>
{getPath(assertion)}
</Tooltip>
<br></br>
</>
) : (
<></>
);

const oneLiner = assertion?.code_context ? (
<>
<span
id={`tooltip_1liner_${uid}`}
className={css(styles.cardHeader1liner)}
>
{assertion.code_context}
</span>
<Tooltip
isOpen={is1LinerTooltipOpen}
target={`tooltip_1liner_${uid}`}
toggle={toggle1LinerTooltip}
style={{maxWidth: "400px"}}
>
{assertion.code_context}
</Tooltip>
</>
) : (
<></>
);

const codeContext = displayPath ? (
<>
<div
className={css(
styles.cardHeaderCode, styles.cardHeaderAlignRight, styles.timeInfo
)}
style={{ order: 6, marginLeft: "10px" }}
>
<span>
{pathButton}
{oneLiner}
</span>
</div>
</>
) : (
<></>
);


const description = assertion.description ? (
assertion.type === "Log" ? (
<Linkify
Expand Down Expand Up @@ -200,6 +249,8 @@ function AssertionHeader({
order: 4,
flexGrow: 4,
padding: ".125rem 0.75rem",
display: "flex",
alignItems: "center",
...assertion.custom_style,
}}
>
Expand All @@ -208,7 +259,7 @@ function AssertionHeader({
<span>({assertion.type})</span>
</span>
{component}
{pathButton}
{codeContext}
{/*
TODO will be implemented when complete permalink feature
linkIcon
Expand All @@ -234,7 +285,11 @@ const renderPath = (assertion) => {
<span className={css(styles.icon)}>
<i className="fa fa-copy fa-s"></i>
</span>
<div className={css(styles.cardHeaderPath)}>{getPath(assertion)}</div>
<div className={css(styles.cardHeaderPath)}>
<span className={css(styles.cardHeaderPathInner)}>
{getWorkspacePath(getPath(assertion))}
</span>
</div>
</>
);
}
Expand Down Expand Up @@ -286,7 +341,7 @@ const styles = StyleSheet.create({

cardHeaderPath: {
float: "right",
fontSize: "small",
//fontSize: "smaller",
maxWidth: "400px",
"-webkit-line-clamp": 1,
"-webkit-box-orient": "vertical",
Expand All @@ -297,6 +352,39 @@ const styles = StyleSheet.create({
textTransform: "none",
},

cardHeader1liner: {
float: "right",
maxWidth: "400px",
"-webkit-line-clamp": 1,
"-webkit-box-orient": "vertical",
"white-space": "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
textTransform: "none",
},

pathButton: {
padding: "0px",
fontFamily: "inherit",
fontSize: "inherit",
lineHeight: "0.8rem",
letterSpacing: "normal",
},

cardHeaderPathInner: {
unicodeBidi: "plaintext",
},

cardHeaderCode: {
display: "flex",
alignItems: "center",
textAlign: "right",
whiteSpace: "pre-wrap",
fontFamily: "monospace",
opacity: "80%",
lineHeight: "0.8rem",
},

collapseDiv: {
paddingLeft: "1.25rem",
},
Expand Down
Loading

0 comments on commit c01e1b5

Please sign in to comment.