def test_zipkin_reporter(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = "http://localhost:{}".format(port) command = [ '-ldebug', '--reporting-zipkin-endpoint={}'.format(endpoint), 'cloc', 'src/python/pants:version' ] pants_run = self.run_pants(command) self.assert_success(pants_run) child_processes = self.find_child_processes_that_send_spans( pants_run.stderr_data) self.assertTrue(child_processes) self.wait_spans_to_be_sent(child_processes) trace = assert_single_element(ZipkinHandler.traces.values()) main_span = self.find_spans_by_name(trace, 'main') self.assertEqual(len(main_span), 1) parent_id = main_span[0]['id'] main_children = self.find_spans_by_parentId(trace, parent_id) self.assertTrue(main_children) self.assertTrue( any(span['name'] == 'cloc' for span in main_children))
def test_download_https() -> None: # This also tests that the custom certs functionality works. with temporary_dir() as temp_dir: def write_resource(name: str) -> Path: path = Path(temp_dir) / name data = pkgutil.get_data("pants.engine.internals", f"fs_test_data/tls/rsa/{name}") assert data is not None path.write_bytes(data) return path server_cert = write_resource("server.crt") server_key = write_resource("server.key") cert_chain = write_resource("server.chain") rule_runner = RuleRunner( rules=[QueryRule(Snapshot, [DownloadFile])], isolated_local_store=True, ca_certs_path=str(cert_chain), ) ssl_context = ssl.SSLContext() ssl_context.load_cert_chain(certfile=str(server_cert), keyfile=str(server_key)) with http_server(StubHandler, ssl_context=ssl_context) as port: snapshot = rule_runner.request( Snapshot, [DownloadFile(f"https://localhost:{port}/file.txt", DOWNLOADS_FILE_DIGEST)], ) assert snapshot.files == ("file.txt",) assert snapshot.digest == DOWNLOADS_EXPECTED_DIRECTORY_DIGEST
def test_zipkin_reports_for_pure_v2_goals(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = "http://localhost:{}".format(port) command = [ '-ldebug', '--no-v1', '--v2', '--reporting-zipkin-endpoint={}'.format(endpoint), '--reporting-zipkin-trace-v2', 'list', '3rdparty:' ] pants_run = self.run_pants(command) self.assert_success(pants_run) child_processes = self.find_child_processes_that_send_spans( pants_run.stderr_data) self.assertTrue(child_processes) self.wait_spans_to_be_sent(child_processes) trace = assert_single_element(ZipkinHandler.traces.values()) v2_span_name_part = "Scandir" self.assertTrue( any(v2_span_name_part in span['name'] for span in trace), "There is no span that contains '{}' in it's name. The trace:{}" .format(v2_span_name_part, trace))
def test_zipkin_reporter_for_v2_engine(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = "http://localhost:{}".format(port) command = [ '-ldebug', '--reporting-zipkin-endpoint={}'.format(endpoint), '--reporting-zipkin-trace-v2', 'cloc', 'examples/src/java/org/pantsbuild/example/hello/simple' ] pants_run = self.run_pants(command) self.assert_success(pants_run) child_processes = self.find_child_processes_that_send_spans(pants_run.stderr_data) self.assertTrue(child_processes) self.wait_spans_to_be_sent(child_processes) trace = assert_single_element(ZipkinHandler.traces.values()) v2_span_name_part = "Scandir" self.assertTrue(any(v2_span_name_part in span['name'] for span in trace), "There is no span that contains '{}' in it's name. The trace:{}".format( v2_span_name_part, trace ))
def test_zipkin_reporter_multi_threads(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = f"http://localhost:{port}" command = [ "-ldebug", f"--reporting-zipkin-endpoint={endpoint}", "compile", "examples/src/scala/org/pantsbuild/example/several_scala_targets::", ] pants_run = self.run_pants(command) self.assert_success(pants_run) child_processes = self.find_child_processes_that_send_spans( pants_run.stderr_data) self.assertTrue(child_processes) self.wait_spans_to_be_sent(child_processes) trace = assert_single_element(ZipkinHandler.traces.values()) rsc_task_span = self.find_spans_by_name_and_service_name( trace, "rsc", "pants/task") self.assertEqual(len(rsc_task_span), 1) rsc_task_span_id = rsc_task_span[0]["id"] compile_workunit_spans = self.find_spans_by_name_and_service_name( trace, "compile", "pants/workunit") self.assertEqual(len(compile_workunit_spans), 4) self.assertTrue( all(span["parentId"] == rsc_task_span_id for span in compile_workunit_spans))
def test_zipkin_reporter(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = f"http://localhost:{port}" command = [ "-ldebug", f"--reporting-zipkin-endpoint={endpoint}", "minimize", "examples/src/java/org/pantsbuild/example/hello/simple", ] pants_run = self.run_pants(command) self.assert_success(pants_run) child_processes = self.find_child_processes_that_send_spans( pants_run.stderr_data) self.assertTrue(child_processes) self.wait_spans_to_be_sent(child_processes) trace = assert_single_element(ZipkinHandler.traces.values()) main_span = self.find_spans_by_name(trace, "main") self.assertEqual(len(main_span), 1) parent_id = main_span[0]["id"] main_children = self.find_spans_by_parentId(trace, parent_id) self.assertTrue(main_children) self.assertTrue( any(span["name"] == "minimize" for span in main_children))
def test_zipkin_reporter_with_given_trace_id_parent_id(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = "http://localhost:{}".format(port) trace_id = "aaaaaaaaaaaaaaaa" parent_span_id = "ffffffffffffffff" command = [ '--reporting-zipkin-endpoint={}'.format(endpoint), '--reporting-zipkin-trace-id={}'.format(trace_id), '--reporting-zipkin-parent-id={}'.format(parent_span_id), 'cloc', 'src/python/pants:version' ] pants_run = self.run_pants(command) self.assert_success(pants_run) num_of_traces = len(ZipkinHandler.traces) self.assertEqual(num_of_traces, 1) trace = ZipkinHandler.traces[-1] main_span = self.find_spans_by_name(trace, 'main') self.assertEqual(len(main_span), 1) main_span_trace_id = main_span[0]['traceId'] self.assertEqual(main_span_trace_id, trace_id) main_span_parent_id = main_span[0]['parentId'] self.assertEqual(main_span_parent_id, parent_span_id) parent_id = main_span[0]['id'] main_children = self.find_spans_by_parentId(trace, parent_id) self.assertTrue(main_children) self.assertTrue(any(span['name'] == 'cloc' for span in main_children))
def test_poll(self): with temporary_dir() as dir: class TestPantsHandler(PantsHandler): def __init__(self, request, client_address, server): # TODO(6071): BaseHTTPServer.BaseHTTPRequestHandler is an old-style class, so we must # invoke its __init__ like this. # This will become unnecessary when we no longer support python2. PantsHandler.__init__( self, settings=ReportingServer.Settings( info_dir=dir, template_dir=dir, assets_dir=dir, root=dir, allowed_clients=['ALL'], ), renderer=None, request=request, client_address=client_address, server=server, ) safe_file_dump(os.path.join(dir, "file"), "hello") with http_server(TestPantsHandler) as port: response = requests.get("http://127.0.0.1:{}/poll?{}".format( port, urlencode({"q": json.dumps([{ "id": "0", "path": "file" }])}), )) self.assertEqual(response.json(), {"0": "hello"})
def test_poll(self): with temporary_dir() as dir: class TestPantsHandler(PantsHandler): def __init__(self, request, client_address, server): super().__init__( settings=ReportingServer.Settings( info_dir=dir, template_dir=dir, assets_dir=dir, root=dir, allowed_clients=['ALL'], ), renderer=None, request=request, client_address=client_address, server=server, ) safe_file_dump(os.path.join(dir, "file"), "hello") with http_server(TestPantsHandler) as port: response = requests.get("http://127.0.0.1:{}/poll?{}".format( port, urlencode({"q": json.dumps([{ "id": "0", "path": "file" }])}), )) self.assertEqual(response.json(), {"0": "hello"})
def test_zipkin_reporter_for_v2_engine(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = f"http://localhost:{port}" command = [ "-ldebug", f"--reporting-zipkin-endpoint={endpoint}", "--reporting-zipkin-trace-v2", "minimize", "examples/src/java/org/pantsbuild/example/hello/simple", ] pants_run = self.run_pants(command) self.assert_success(pants_run) child_processes = self.find_child_processes_that_send_spans( pants_run.stderr_data) self.assertTrue(child_processes) self.wait_spans_to_be_sent(child_processes) trace = assert_single_element(ZipkinHandler.traces.values()) v2_span_name_part = "Snapshot" self.assertTrue( any(v2_span_name_part in span["name"] for span in trace), "There is no span that contains '{}' in it's name. The trace:{}" .format(v2_span_name_part, trace), )
def test_zipkin_reports_for_pure_v2_goals(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = f"http://localhost:{port}" command = [ "-ldebug", "--no-v1", "--v2", f"--reporting-zipkin-endpoint={endpoint}", "--reporting-zipkin-trace-v2", "list", "3rdparty:", ] pants_run = self.run_pants(command) self.assert_success(pants_run) child_processes = self.find_child_processes_that_send_spans( pants_run.stderr_data) self.assertTrue(child_processes) self.wait_spans_to_be_sent(child_processes) trace = assert_single_element(ZipkinHandler.traces.values()) v2_span_name_part = "Snapshot" self.assertTrue( any(v2_span_name_part in span["name"] for span in trace), "There is no span that contains '{}' in it's name. The trace:{}" .format(v2_span_name_part, trace), )
def test_zipkin_reporter_multi_threads(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = "http://localhost:{}".format(port) command = [ '--reporting-zipkin-endpoint={}'.format(endpoint), 'compile', 'examples/src/scala/org/pantsbuild/example/several_scala_targets::' ] pants_run = self.run_pants(command) self.assert_success(pants_run) trace = assert_single_element(ZipkinHandler.traces.values()) zinc_task_span = self.find_spans_by_name_and_service_name( trace, 'zinc', 'pants task') self.assertEqual(len(zinc_task_span), 1) zinc_task_span_id = zinc_task_span[0]['id'] compile_workunit_spans = self.find_spans_by_name_and_service_name( trace, 'compile', 'pants workunit') self.assertEqual(len(compile_workunit_spans), 3) self.assertTrue( all(span['parentId'] == zinc_task_span_id for span in compile_workunit_spans))
def test_download_missing_file(downloads_rule_runner: RuleRunner) -> None: with pytest.raises(ExecutionError) as exc: with http_server(StubHandler) as port: downloads_rule_runner.request( Snapshot, [DownloadFile(f"http://localhost:{port}/notfound", DOWNLOADS_FILE_DIGEST)] ) assert "404" in str(exc.value)
def test_download_missing_file(self): with self.isolated_local_store(): with http_server(StubHandler) as port: url = UrlToFetch("http://localhost:{}/notfound".format(port), self.pantsbuild_digest) with self.assertRaises(ExecutionError) as cm: self.scheduler.product_request(Snapshot, subjects=[url]) self.assertIn('404', str(cm.exception))
def test_download_valid(downloads_rule_runner: RuleRunner) -> None: with http_server(StubHandler) as port: snapshot = downloads_rule_runner.request( Snapshot, [DownloadFile(f"http://localhost:{port}/file.txt", DOWNLOADS_FILE_DIGEST)] ) assert snapshot.files == ("file.txt",) assert snapshot.digest == DOWNLOADS_EXPECTED_DIRECTORY_DIGEST
def test_download_https(self) -> None: # Note that this also tests that the custom certs functionality works. with temporary_dir() as temp_dir: def write_resource(name: str) -> Path: path = Path(temp_dir) / name data = pkgutil.get_data("pants.engine.internals", f"tls_testing/rsa/{name}") assert data is not None path.write_bytes(data) return path server_cert = write_resource("server.crt") server_key = write_resource("server.key") cert_chain = write_resource("server.chain") scheduler = self.mk_scheduler( rules=[*fs_rules(), QueryRule(Snapshot, (DownloadFile, ))], ca_certs_path=str(cert_chain), ) with self.isolated_local_store(): ssl_context = ssl.SSLContext() ssl_context.load_cert_chain(certfile=str(server_cert), keyfile=str(server_key)) with http_server(StubHandler, ssl_context=ssl_context) as port: snapshot = self.execute( scheduler, Snapshot, DownloadFile(f"https://localhost:{port}/file.txt", self.file_digest), )[0] self.assert_snapshot_equals(snapshot, ["file.txt"], self.expected_snapshot_digest)
def test_poll(self): with temporary_dir() as dir: class TestPantsHandler(PantsHandler): def __init__(self, request, client_address, server): # TODO(6071): BaseHTTPServer.BaseHTTPRequestHandler is an old-style class, so we must # invoke its __init__ like this. # This will become unnecessary when we no longer support python2. PantsHandler.__init__( self, settings=ReportingServer.Settings( info_dir=dir, template_dir=dir, assets_dir=dir, root=dir, allowed_clients=['ALL'], ), renderer=None, request=request, client_address=client_address, server=server, ) safe_file_dump(os.path.join(dir, "file"), "hello") with http_server(TestPantsHandler) as port: response = requests.get("http://127.0.0.1:{}/poll?{}".format( port, urlencode({"q": json.dumps([{"id": "0", "path": "file"}])}), )) self.assertEqual(response.json(), {"0": "hello"})
def test_download_missing_file(self): with self.isolated_local_store(): with http_server(StubHandler) as port: url = UrlToFetch(f"http://localhost:{port}/notfound", self.pantsbuild_digest) with self.assertRaises(ExecutionError) as cm: self.scheduler.product_request(Snapshot, subjects=[url]) self.assertIn('404', str(cm.exception))
def test_zipkin_reporter_with_given_trace_id_parent_id(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = "http://localhost:{}".format(port) trace_id = "aaaaaaaaaaaaaaaa" parent_span_id = "ffffffffffffffff" command = [ '--reporting-zipkin-endpoint={}'.format(endpoint), '--reporting-zipkin-trace-id={}'.format(trace_id), '--reporting-zipkin-parent-id={}'.format(parent_span_id), 'cloc', 'src/python/pants:version' ] pants_run = self.run_pants(command) self.assert_success(pants_run) trace = assert_single_element(ZipkinHandler.traces.values()) main_span = self.find_spans_by_name(trace, 'main') self.assertEqual(len(main_span), 1) main_span_trace_id = main_span[0]['traceId'] self.assertEqual(main_span_trace_id, trace_id) main_span_parent_id = main_span[0]['parentId'] self.assertEqual(main_span_parent_id, parent_span_id) parent_id = main_span[0]['id'] main_children = self.find_spans_by_parentId(trace, parent_id) self.assertTrue(main_children) self.assertTrue( any(span['name'] == 'cloc' for span in main_children))
def test_download_missing_file(self) -> None: with self.isolated_local_store(): with http_server(StubHandler) as port: url = UrlToFetch(f"http://localhost:{port}/notfound", self.pantsbuild_digest) with self.assertRaises(ExecutionError) as cm: self.request_single_product(Snapshot, url) assert "404" in str(cm.exception)
def test_download(self): with self.isolated_local_store(): with http_server(StubHandler) as port: url = UrlToFetch("http://localhost:{}/CNAME".format(port), self.pantsbuild_digest) snapshot, = self.scheduler.product_request(Snapshot, subjects=[url]) self.assert_snapshot_equals(snapshot, ["CNAME"], Digest( text_type("16ba2118adbe5b53270008790e245bbf7088033389461b08640a4092f7f647cf"), 81 ))
def test_download_wrong_digest(downloads_rule_runner: RuleRunner) -> None: file_digest = FileDigest(DOWNLOADS_FILE_DIGEST.fingerprint, DOWNLOADS_FILE_DIGEST.serialized_bytes_length + 1) with pytest.raises(ExecutionError) as exc: with http_server(StubHandler) as port: downloads_rule_runner.request(Snapshot, [ DownloadFile(f"http://localhost:{port}/file.txt", file_digest) ]) assert "wrong digest" in str(exc.value).lower()
def test_download(self): with self.isolated_local_store(): with http_server(StubHandler) as port: url = UrlToFetch(f"http://localhost:{port}/CNAME", self.pantsbuild_digest) snapshot, = self.scheduler.product_request(Snapshot, subjects=[url]) self.assert_snapshot_equals(snapshot, ["CNAME"], Digest( "16ba2118adbe5b53270008790e245bbf7088033389461b08640a4092f7f647cf", 81 ))
def test_download_missing_file(self) -> None: with self.isolated_local_store(): with http_server(StubHandler) as port: with self.assertRaises(ExecutionError) as cm: self.request( Snapshot, [ DownloadFile(f"http://localhost:{port}/notfound", self.file_digest) ], ) assert "404" in str(cm.exception)
def test_download_wrong_digest(self): with self.isolated_local_store(): with http_server(StubHandler) as port: url = UrlToFetch( "http://localhost:{}/CNAME".format(port), Digest( self.pantsbuild_digest.fingerprint, self.pantsbuild_digest.serialized_bytes_length + 1, ), ) with self.assertRaises(ExecutionError) as cm: self.scheduler.product_request(Snapshot, subjects=[url]) self.assertIn('wrong digest', str(cm.exception).lower())
def test_download_wrong_digest(self) -> None: with self.isolated_local_store(): with http_server(StubHandler) as port: url = UrlToFetch( f"http://localhost:{port}/CNAME", Digest( self.pantsbuild_digest.fingerprint, self.pantsbuild_digest.serialized_bytes_length + 1, ), ) with self.assertRaises(ExecutionError) as cm: self.request_single_product(Snapshot, url) assert "wrong digest" in str(cm.exception).lower()
def test_download_wrong_digest(self): with self.isolated_local_store(): with http_server(StubHandler) as port: url = UrlToFetch( f"http://localhost:{port}/CNAME", Digest( self.pantsbuild_digest.fingerprint, self.pantsbuild_digest.serialized_bytes_length + 1, ), ) with self.assertRaises(ExecutionError) as cm: self.scheduler.product_request(Snapshot, subjects=[url]) self.assertIn('wrong digest', str(cm.exception).lower())
def test_download(self) -> None: with self.isolated_local_store(): with http_server(StubHandler) as port: snapshot = self.request_single_product( Snapshot, DownloadFile( f"http://localhost:{port}/do_not_remove_or_edit.txt", self.pantsbuild_digest ), ) self.assert_snapshot_equals( snapshot, ["do_not_remove_or_edit.txt"], Digest("03bb499daabafc60212d2f4b2fab49b47b35b83a90c056224c768d52bce02691", 102), )
def test_download_wrong_digest(self) -> None: with self.isolated_local_store(): with http_server(StubHandler) as port: with self.assertRaises(ExecutionError) as cm: self.request_single_product( Snapshot, DownloadFile( f"http://localhost:{port}/do_not_remove_or_edit.txt", Digest( self.pantsbuild_digest.fingerprint, self.pantsbuild_digest.serialized_bytes_length + 1, ), ), ) assert "wrong digest" in str(cm.exception).lower()
def test_download_caches(downloads_rule_runner: RuleRunner) -> None: # We would error if we hit the HTTP server with 404, but we're not going to hit the HTTP # server because it's cached, so we shouldn't see an error. prime_store_with_roland_digest(downloads_rule_runner) with http_server(StubHandler) as port: download_file = DownloadFile( f"http://localhost:{port}/roland", FileDigest( "693d8db7b05e99c6b7a7c0616456039d89c555029026936248085193559a0b5d", 16), ) snapshot = downloads_rule_runner.request(Snapshot, [download_file]) assert snapshot.files == ("roland", ) assert snapshot.digest == Digest( "9341f76bef74170bedffe51e4f2e233f61786b7752d21c2339f8ee6070eba819", 82)
def test_download(self) -> None: with self.isolated_local_store(): with http_server(StubHandler) as port: snapshot = self.request( Snapshot, [ DownloadFile(f"http://localhost:{port}/file.txt", self.file_digest) ], ) self.assert_snapshot_equals( snapshot, ["file.txt"], self.expected_snapshot_digest, )
def test_zipkin_reporter_with_zero_sample_rate(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = "http://localhost:{}".format(port) command = [ '--reporting-zipkin-endpoint={}'.format(endpoint), '--reporting-zipkin-sample-rate=0.0', 'cloc', 'src/python/pants:version' ] pants_run = self.run_pants(command) self.assert_success(pants_run) num_of_traces = len(ZipkinHandler.traces) self.assertEqual(num_of_traces, 0)
def test_zipkin_reporter_with_zero_sample_rate(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = "http://localhost:{}".format(port) command = [ '--reporting-zipkin-endpoint={}'.format(endpoint), '--reporting-zipkin-sample-rate=0.0', 'cloc', 'src/python/pants:version' ] pants_run = self.run_pants(command) self.assert_success(pants_run) num_of_traces = len(ZipkinHandler.traces) self.assertEqual(num_of_traces, 0)
def test_caches_downloads(self): with self.isolated_local_store(): with http_server(StubHandler) as port: self.prime_store_with_roland_digest() # This would error if we hit the HTTP server, because 404, # but we're not going to hit the HTTP server because it's cached, # so we shouldn't see an error... url = UrlToFetch( "http://localhost:{}/roland".format(port), Digest('693d8db7b05e99c6b7a7c0616456039d89c555029026936248085193559a0b5d', 16), ) snapshot, = self.scheduler.product_request(Snapshot, subjects=[url]) self.assert_snapshot_equals(snapshot, ["roland"], Digest( text_type("9341f76bef74170bedffe51e4f2e233f61786b7752d21c2339f8ee6070eba819"), 82 ))
def test_caches_downloads(self): with self.isolated_local_store(): with http_server(StubHandler) as port: self.prime_store_with_roland_digest() # This would error if we hit the HTTP server, because 404, # but we're not going to hit the HTTP server because it's cached, # so we shouldn't see an error... url = UrlToFetch( f"http://localhost:{port}/roland", Digest('693d8db7b05e99c6b7a7c0616456039d89c555029026936248085193559a0b5d', 16), ) snapshot, = self.scheduler.product_request(Snapshot, subjects=[url]) self.assert_snapshot_equals(snapshot, ["roland"], Digest( "9341f76bef74170bedffe51e4f2e233f61786b7752d21c2339f8ee6070eba819", 82 ))
def test_download_wrong_digest(self) -> None: with self.isolated_local_store(): with http_server(StubHandler) as port: with self.assertRaises(ExecutionError) as cm: self.request( Snapshot, [ DownloadFile( f"http://localhost:{port}/file.txt", FileDigest( self.file_digest.fingerprint, self.file_digest.serialized_bytes_length + 1, ), ) ], ) assert "wrong digest" in str(cm.exception).lower()
def test_zipkin_reporter_with_zero_sample_rate(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = f"http://localhost:{port}" command = [ '-ldebug', f'--reporting-zipkin-endpoint={endpoint}', '--reporting-zipkin-sample-rate=0.0', 'cloc', 'examples/src/java/org/pantsbuild/example/hello/simple' ] pants_run = self.run_pants(command) self.assert_success(pants_run) child_processes = self.find_child_processes_that_send_spans( pants_run.stderr_data) self.assertFalse(child_processes) num_of_traces = len(ZipkinHandler.traces) self.assertEqual(num_of_traces, 0)
def test_zipkin_reporter_for_v2_engine(self): ZipkinHandler = zipkin_handler() with http_server(ZipkinHandler) as port: endpoint = "http://localhost:{}".format(port) command = [ '--reporting-zipkin-endpoint={}'.format(endpoint), '--reporting-zipkin-trace-v2', 'cloc', 'src/python/pants:version' ] pants_run = self.run_pants(command) self.assert_success(pants_run) num_of_traces = len(ZipkinHandler.traces) self.assertEqual(num_of_traces, 1) trace = ZipkinHandler.traces[-1] v2_span_name_part = "Scandir" self.assertTrue(any(v2_span_name_part in span['name'] for span in trace), "There is no span that contains '{}' in it's name. The trace:{}".format( v2_span_name_part, trace ))