v8/tools/gcmole/gcmole_test.py
Michael Achenbach 7136ea89d2 [gcmole] Fix and simplify test-run mode
The test-run mode was broken after output improvements and the
introduction of pathlib.

This fixes the string concatenation with paths and updates the test
output to match the status quo. This also changes the test-run mode
to run exclusively when the --test-run option is passed. Now it's
either a test run or a normal run. Like that we can add the test run
as a separate test step on a bot. If both are needed in sequence
for something, gcmole could be called twice.

Bug: v8:12660
Change-Id: I58179d50950fa76d8f66b974325a8fed84dc91b6
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4075727
Commit-Queue: Michael Achenbach <machenbach@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84655}
2022-12-05 15:21:19 +00:00

339 lines
10 KiB
Python

#!/usr/bin/env python3
# Copyright 2022 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from pathlib import Path
import collections
import os
import re
import tempfile
import textwrap
import unittest
import gcmole
TESTDATA_PATH = os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'testdata', 'v8')
Options = collections.namedtuple(
'Options', ['v8_root_dir', 'v8_target_cpu', 'test_run'])
def abs_test_file(f):
return Path(os.path.join(TESTDATA_PATH, f))
class FilesTest(unittest.TestCase):
def testFileList_for_testing(self):
options = Options(Path(TESTDATA_PATH), 'x64', True)
self.assertEqual(
gcmole.build_file_list(options),
list(map(abs_test_file, ['tools/gcmole/gcmole-test.cc'])))
def testFileList_x64(self):
options = Options(Path(TESTDATA_PATH), 'x64', False)
expected = [
'file1.cc',
'file2.cc',
'x64/file1.cc',
'x64/file2.cc',
'file3.cc',
'file4.cc',
'test/cctest/test-x64-file1.cc',
'test/cctest/test-x64-file2.cc',
]
self.assertEqual(
gcmole.build_file_list(options),
list(map(abs_test_file, expected)))
def testFileList_arm(self):
options = Options(Path(TESTDATA_PATH), 'arm', False)
expected = [
'file1.cc',
'file2.cc',
'file3.cc',
'file4.cc',
'arm/file1.cc',
'arm/file2.cc',
]
self.assertEqual(
gcmole.build_file_list(options),
list(map(abs_test_file, expected)))
GC = 'Foo,NowCollectAllTheGarbage'
SP = 'Bar,SafepointSlowPath'
WF = 'Baz,WriteField'
class OutputLines:
CALLERS_RE = re.compile(r'([\w,]+)\s*→\s*(.*)')
def __init__(self, *callee_list):
"""Construct a test data placeholder for output lines of one invocation of
the GCMole plugin.
Args:
callee_list: Strings, each containing a caller/calle relationship
formatted as "A → B C", meaning A calls B and C. For GC,
Safepoint and a allow-listed function use GC, SP and WF
constants above respectivly.
Methods not calling anything are formatted as "A →".
"""
self.callee_list = callee_list
def lines(self):
result = []
for str_rep in self.callee_list:
match = self.CALLERS_RE.match(str_rep)
assert match
result.append(match.group(1))
for callee in (match.group(2) or '').split():
result.append('\t' + callee)
return result
class SuspectCollectorTest(unittest.TestCase):
def create_callgraph(self, *outputs):
call_graph = gcmole.CallGraph()
for output in outputs:
call_graph.parse(output.lines())
return call_graph
def testCallGraph(self):
call_graph = self.create_callgraph(OutputLines())
self.assertDictEqual(call_graph.funcs, {})
call_graph = self.create_callgraph(OutputLines('A →'))
self.assertDictEqual(call_graph.funcs, {'A': set()})
call_graph = self.create_callgraph(OutputLines('A → B'))
self.assertDictEqual(call_graph.funcs, {'A': set(), 'B': set('A')})
call_graph = self.create_callgraph(
OutputLines('A → B C', 'B → C D', 'D →'))
self.assertDictEqual(
call_graph.funcs,
{'A': set(), 'B': set('A'), 'C': set(['A', 'B']), 'D': set('B')})
call_graph = self.create_callgraph(
OutputLines('B → C D', 'D →'), OutputLines('A → B C'))
self.assertDictEqual(
call_graph.funcs,
{'A': set(), 'B': set('A'), 'C': set(['A', 'B']), 'D': set('B')})
def create_collector(self, outputs):
Options = collections.namedtuple('OptionsForCollector', ['allowlist'])
options = Options(True)
call_graph = self.create_callgraph(*outputs)
collector = gcmole.GCSuspectsCollector(options, call_graph.funcs)
collector.propagate()
return collector
def check(self, outputs, expected_gc, expected_gc_caused):
"""Verify the GCSuspectsCollector propagation and outputs against test
data.
Args:
outputs: List of OutputLines object simulating the lines returned by
the GCMole plugin in drop-callees mode. Each output lines object
represents one plugin invocation.
expected_gc: Mapping as expected by GCSuspectsCollector.gc.
expected_gc_caused: Mapping as expected by GCSuspectsCollector.gc_caused.
"""
collector = self.create_collector(outputs)
self.assertDictEqual(collector.gc, expected_gc)
self.assertDictEqual(collector.gc_caused, expected_gc_caused)
def testNoGC(self):
self.check(
outputs=[OutputLines()],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A →')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A →', 'B →')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B C')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B', 'B → C')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B C', 'B → D', 'D → A', 'C →')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A →'), OutputLines('B →')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B'), OutputLines('B → C')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B C'),
OutputLines('B → D', 'D → A'),
OutputLines('C →')],
expected_gc={},
expected_gc_caused={},
)
def testGCOneFile(self):
self.check(
outputs=[OutputLines(f'{GC}')],
expected_gc={GC: True},
expected_gc_caused={GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}')],
expected_gc={GC: True, 'A': True},
expected_gc_caused={'A': {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', 'B → A')],
expected_gc={GC: True, 'A': True, 'B': True},
expected_gc_caused={'B': {'A'}, 'A': {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines('B → A', f'A → {GC}')],
expected_gc={GC: True, 'A': True, 'B': True},
expected_gc_caused={'B': {'A'}, 'A': {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → B {GC}', 'B →', 'C → B A')],
expected_gc={GC: True, 'A': True, 'C': True},
expected_gc_caused={'C': {'A'}, 'A': {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', 'B → A', 'C → A', 'D → B C')],
expected_gc={GC: True, 'A': True, 'B': True, 'C': True, 'D': True},
expected_gc_caused={'C': {'A'}, 'A': {GC}, 'B': {'A'}, 'D': {'B', 'C'},
GC: {'<GC>'}},
)
def testAllowListOneFile(self):
self.check(
outputs=[OutputLines(f'{WF}')],
expected_gc={WF: False},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines(f'{WF}{GC}')],
expected_gc={GC: True, WF: False},
expected_gc_caused={WF: {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', f'{WF} → A B', 'D → A B',
f'E → {WF}')],
expected_gc={GC: True, WF: False, 'A': True, 'D': True},
expected_gc_caused={'A': {GC}, WF: {'A'}, 'D': {'A'}, GC: {'<GC>'}},
)
def testSafepointOneFile(self):
self.check(
outputs=[OutputLines(f'{SP}')],
expected_gc={SP: True},
expected_gc_caused={SP: {'<Safepoint>'}},
)
self.check(
outputs=[OutputLines('B → A', f'A → {SP}')],
expected_gc={SP: True, 'A': True, 'B': True},
expected_gc_caused={'B': {'A'}, 'A': {SP}, SP: {'<Safepoint>'}},
)
def testCombinedOneFile(self):
self.check(
outputs=[OutputLines(f'{GC}', f'{SP}')],
expected_gc={SP: True, GC: True},
expected_gc_caused={SP: {'<Safepoint>'}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', f'B → {SP}')],
expected_gc={GC: True, SP: True, 'A': True, 'B': True},
expected_gc_caused={'B': {SP}, 'A': {GC}, SP: {'<Safepoint>'},
GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', f'B → {SP}', 'C → D A B')],
expected_gc={GC: True, SP: True, 'A': True, 'B': True, 'C': True},
expected_gc_caused={'B': {SP}, 'A': {GC}, 'C': {'A', 'B'},
SP: {'<Safepoint>'}, GC: {'<GC>'}},
)
def testCombinedMoreFiles(self):
self.check(
outputs=[OutputLines(f'A → {GC}'), OutputLines(f'B → {SP}')],
expected_gc={GC: True, SP: True, 'A': True, 'B': True},
expected_gc_caused={'B': {SP}, 'A': {GC}, SP: {'<Safepoint>'},
GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}'), OutputLines(f'B → {SP}'),
OutputLines('C → D A B')],
expected_gc={GC: True, SP: True, 'A': True, 'B': True, 'C': True},
expected_gc_caused={'B': {SP}, 'A': {GC}, 'C': {'A', 'B'},
SP: {'<Safepoint>'}, GC: {'<GC>'}},
)
def testWriteGCMoleResults(self):
temp_dir = Path(tempfile.mkdtemp('gcmole_test'))
Options = collections.namedtuple('OptionsForWriting', ['v8_target_cpu'])
collector = self.create_collector(
[OutputLines(f'A → {GC}'), OutputLines(f'B → {SP}')])
gcmole.write_gcmole_results(collector, Options('x64'), temp_dir)
gcsuspects_expected = textwrap.dedent(f"""\
{GC}
{SP}
A
B
""")
with open(temp_dir / 'gcsuspects') as f:
self.assertEqual(f.read(), gcsuspects_expected)
gccauses_expected = textwrap.dedent(f"""
{GC}
start,nested
<GC>
end,nested
{SP}
start,nested
<Safepoint>
end,nested
A
start,nested
{GC}
end,nested
B
start,nested
{SP}
end,nested
""").strip()
with open(temp_dir / 'gccauses') as f:
self.assertEqual(f.read().strip(), gccauses_expected)
if __name__ == '__main__':
unittest.main()