Skip to content

Commit fb4fb61

Browse files
authored
Update CSV output for PerfLab (#1030)
This PR adds the following CLI flags: --perf-run: takes no arguments; used for data sources that do not have RunAsPerf set in their .ini file. --iteration: takes a single integer argument; if unspecified, defaults to 0.
1 parent ae3b015 commit fb4fb61

11 files changed

+229
-77
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ The latest version of the SDK is always targeted towards the latest, non-beta ve
2020
| Connector SDK for Tableau 2020.1 | 5-08-2020 |
2121
| Connector SDK for Tableau 2019.4 | 3-13-2020 |
2222
| Connector Packager SDK (Beta) for Tableau 2019.3 | 12-11-2019 |
23-
| TDVT | 2.6.2 (03-22-2022) |
23+
| TDVT | 2.7.0 (10-28-2022) |
2424
| | 1.5.24 (04-13-2020) |
2525
| Connector Packager | 2.1.0 (05-08-2020) |
2626

tdvt/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [2.7.0]. - 2022-10-28
8+
- Add a `--perf-run` flag that changes the output of `tdvt_log_combined.csv`.
9+
710
## [2.6.2] - 2022-03-22
811
- Update file encoding of setup.string.regex.icu_fallback.txt to utf-8.
912

tdvt/tdvt/config_gen/datasource_list.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import glob
88
import os.path
99
import logging
10+
from typing import List, Optional
1011

1112
from .gentests import list_configs, list_config
1213
from ..resources import *
@@ -407,12 +408,12 @@ def interpret_ds_list(self, ds_list, built_list=None):
407408
def add_test(self, test_config):
408409
self.dsnames[test_config.dsname] = test_config
409410

410-
def get_datasource_info(self, dsname):
411+
def get_datasource_info(self, dsname) -> Optional[TestConfig]:
411412
if dsname in self.dsnames:
412413
return self.dsnames[dsname]
413414
return None
414415

415-
def get_datasources(self, suite):
416+
def get_datasources(self, suite) -> Optional[List]:
416417
ds_to_run = []
417418
if not suite:
418419
return

tdvt/tdvt/config_gen/tdvtconfig.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(self, from_args=None, from_json=None, test_config: TestConfig = Non
3030
self.tds = ''
3131
self.tested_run_time_config = None
3232
self.tabquery_path = ''
33+
self.iteration: int = 0
3334

3435
if from_args:
3536
self.init_from_args(from_args)
@@ -42,7 +43,7 @@ def __init__(self, from_args=None, from_json=None, test_config: TestConfig = Non
4243
def set_run_time_test_config(self, rtt: RunTimeTestConfig):
4344
self.timeout_seconds = rtt.timeout_seconds
4445
self.d_override = rtt.d_override
45-
self.run_as_perf = rtt.run_as_perf
46+
self.run_as_perf = rtt.run_as_perf or self.run_as_perf
4647
self.tested_run_time_config = rtt
4748
if rtt.tabquery_paths:
4849
self.tabquery_path = rtt.tabquery_paths.to_array()
@@ -60,6 +61,10 @@ def init_from_args(self, args):
6061
self.verbose = args.verbose
6162
if args.custom_output_dir:
6263
self.custom_output_dir = args.custom_output_dir
64+
if args.perf_run:
65+
self.run_as_perf = True
66+
if args.perf_iteration:
67+
self.iteration = args.perf_iteration
6368

6469
def init_from_json(self, json):
6570
self.tested_sql = json.get('tested_sql', self.tested_sql)

tdvt/tdvt/constants.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
DEFAULT_CSV_HEADERS = [
2+
'Suite',
3+
'Test Set',
4+
'TDSName',
5+
'TestName',
6+
'TestPath',
7+
'Passed',
8+
'Closest Expected',
9+
'Diff count',
10+
'Test Case',
11+
'Test Type',
12+
'Priority',
13+
'Categories',
14+
'Functions',
15+
'Process Output',
16+
'Error Msg',
17+
'Error Type',
18+
'Query Time (ms)',
19+
'Generated SQL',
20+
]
21+
22+
PERFLAB_CSV_HEADERS = [
23+
"TestGroup",
24+
"TestSubGroup",
25+
"Test",
26+
"TestComment1",
27+
"TestComment2",
28+
"TestComment3",
29+
"Iteration",
30+
"IterationStartTime",
31+
"IterationEndTime",
32+
"ErrorString",
33+
"IterationComment1",
34+
"IterationComment2",
35+
"IterationComment3",
36+
"MetricResourceType",
37+
"MetricResourceInstance",
38+
"Result"
39+
]
40+
41+
TUPLE_DISPLAY_LIMIT = 100

tdvt/tdvt/tabquery.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import configparser
2-
import os
32
import sys
43

54
from .resources import *
@@ -38,14 +37,15 @@ def get_max_process_level_of_parallelization(desired_threads):
3837
return 1
3938
return desired_threads
4039

40+
4141
def build_tabquery_command_line(work):
4242
try:
4343
sys.path.insert(0, get_extensions_dir())
4444
from extend_tabquery import TabqueryCommandLineExtension
4545
sys.path.pop(0)
4646
tb = TabqueryCommandLineExtension()
4747
logging.debug("Imported extension extend_tabquery")
48-
except:
48+
except ImportError:
4949
tb = TabqueryCommandLine()
5050

5151
cmdline = tb.build_tabquery_command_line(work)

tdvt/tdvt/tdvt.py

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Test driver script for the Tableau Datasource Verification Tool
33
44
"""
5+
56
import sys
67

78
if sys.version_info[0] < 3:
@@ -21,12 +22,12 @@
2122
from pathlib import Path
2223
from typing import List, Optional, Tuple, Union
2324

24-
from .config_gen.datasource_list import print_ds, print_configurations, print_logical_configurations
25+
from .config_gen.datasource_list import TestRegistry, print_ds, print_configurations, print_logical_configurations
2526
from .config_gen.tdvtconfig import TdvtInvocation
2627
from .config_gen.test_config import TestSet, SingleLogicalTestSet, SingleExpressionTestSet, FileTestSet, TestConfig, RunTimeTestConfig
2728
from .setup_env import create_test_environment, add_datasource
2829
from .tabquery import *
29-
from .tdvt_core import generate_files, run_diff, run_tests, run_connectors_test_core
30+
from .tdvt_core import generate_files, run_diff, run_tests, run_connectors_test_core, return_csv_dialect
3031
from .version import __version__
3132

3233
# This contains the dictionary of configs you can run.
@@ -45,43 +46,46 @@ class TestOutputFiles(object):
4546
combined_output = []
4647

4748
@classmethod
48-
def copy_output_file(c, src_name, src_dir):
49+
def copy_output_file(cls, src_name, src_dir, csv_dialect):
4950
src = os.path.join(src_dir, src_name)
5051
logging.debug("Copying {0} to output".format(src))
5152
try:
5253
with open(src, 'r', encoding='utf8') as src_file:
53-
reader = csv.DictReader(src_file, dialect='tdvt')
54+
reader = csv.DictReader(src_file, dialect=csv_dialect)
5455
for row in reader:
55-
c.combined_output.append(row)
56+
cls.combined_output.append(row)
5657

5758
except IOError as e:
5859
logging.debug("Exception while copying files: " + str(e))
5960
return
6061

6162
@classmethod
62-
def write_test_results_csv(c, custom_output_dir: str=''):
63-
if not c.combined_output:
63+
def write_test_results_csv(cls, perf_run: bool, custom_output_dir: str = ''):
64+
if not cls.combined_output:
6465
logging.debug("write_test_results_csv called with no test output")
6566
return
6667

67-
logging.debug("Copying output to {0}".format(c.output_csv))
68+
logging.debug("Copying output to {0}".format(cls.output_csv))
6869
# Sort combined_output on the number of distinct functions (order of complexity)
6970
sort_by_complexity = lambda row: len(row['Functions'].split(','))
7071
try:
71-
c.combined_output.sort(key=sort_by_complexity)
72+
cls.combined_output.sort(key=sort_by_complexity)
7273
except KeyError as e:
7374
logging.debug("Tried to sort output on a key that doesn't exist. Leaving output unsorted.")
7475

75-
dst = os.path.join(os.getcwd(), c.output_csv)
76+
dst = os.path.join(os.getcwd(), cls.output_csv)
7677
if custom_output_dir != '':
77-
dst = os.path.join(Path(custom_output_dir), c.output_csv)
78+
dst = os.path.join(Path(custom_output_dir), cls.output_csv)
7879
try:
79-
dst_exists = os.path.isfile(dst)
8080
with open(dst, 'w', encoding='utf8') as dst_file:
81-
writer = csv.DictWriter(dst_file, fieldnames=c.combined_output[0],
82-
dialect='tdvt', quoting=csv.QUOTE_MINIMAL)
81+
writer = csv.DictWriter(
82+
dst_file,
83+
fieldnames=cls.combined_output[0],
84+
dialect=return_csv_dialect(perf_run),
85+
quoting=csv.QUOTE_MINIMAL
86+
)
8387
writer.writeheader()
84-
for row in c.combined_output:
88+
for row in cls.combined_output:
8589
writer.writerow(row)
8690
except IOError as e:
8791
logging.debug("Exception while writing to file: " + str(e))
@@ -95,7 +99,7 @@ def do_test_queue_work(i, q):
9599
abort_test_run = False
96100
while True:
97101
# This blocks if the queue is empty.
98-
work = q.get()
102+
work: TestRunner = q.get()
99103

100104
work.run()
101105

@@ -135,8 +139,8 @@ def copy_files_to_zip(self, dst_file_name, src_dir, is_logs):
135139
inner_output = os.path.join(optional_dir_name, file_to_be_zipped)
136140
myzip.write(actual, inner_output)
137141

138-
def copy_output_files(self):
139-
TestOutputFiles.copy_output_file("test_results.csv", self.temp_dir)
142+
def copy_output_files(self, csv_dialect):
143+
TestOutputFiles.copy_output_file("test_results.csv", self.temp_dir, csv_dialect)
140144

141145
def copy_test_result_file(self):
142146
src = os.path.join(self.temp_dir, "tdvt_output.json")
@@ -179,10 +183,12 @@ def copy_test_result_file(self):
179183

180184
def copy_files_and_cleanup(self):
181185
left_temp_dir = False
186+
csv_dialect = 'perflab' if self.test_config.run_as_perf else 'tdvt'
187+
# if
182188
try:
183189
self.copy_files_to_zip(TestOutputFiles.output_actuals, self.temp_dir, is_logs=False)
184190
self.copy_files_to_zip(TestOutputFiles.output_tabquery_log, self.temp_dir, is_logs=True)
185-
self.copy_output_files()
191+
self.copy_output_files(csv_dialect)
186192
self.copy_test_result_file()
187193
except Exception as e:
188194
print(e)
@@ -243,7 +249,9 @@ def get_datasource_registry(platform):
243249
return reg
244250

245251

246-
def enqueue_single_test(args, ds_info: TestConfig, suite) -> Union[Tuple[None, None], Tuple[Union[SingleLogicalTestSet, SingleExpressionTestSet], TdvtInvocation]]: # noqa: E501
252+
def enqueue_single_test(
253+
args, ds_info: TestConfig, suite
254+
) -> Union[Tuple[None, None], Tuple[Union[SingleLogicalTestSet, SingleExpressionTestSet], TdvtInvocation]]:
247255
if not args.command == 'run-pattern' or not args.tds_pattern or (args.logical_pattern and args.expression_pattern):
248256
return None, None
249257

@@ -483,6 +491,8 @@ def create_parser():
483491
run_test_common_parser.add_argument('--output-dir', '-o', dest='custom_output_dir',
484492
help='Writes log files to a specified directory. The directory must exist.',
485493
required=False, default=None, const='*', nargs='?')
494+
run_test_common_parser.add_argument('--perf-run', dest='perf_run', action='store_true', default=False)
495+
run_test_common_parser.add_argument('--iteration', dest='perf_iteration', type=int)
486496
subparsers = parser.add_subparsers(help='commands', dest='command')
487497

488498
#Get information.
@@ -507,7 +517,6 @@ def create_parser():
507517
run_test_parser.add_argument('--logical', '-q', dest='logical_only', help='Only run logical tests whose config file name matches the supplied string, or all if blank.', required=False, default=None, const='*', nargs='?')
508518
run_test_parser.add_argument('--expression', '-e', dest='expression_only', help='Only run expression tests whose config file name matches the suppled string, or all if blank.', required=False, default=None, const='*', nargs='?')
509519

510-
511520
#Run test pattern.
512521
run_test_pattern_parser = subparsers.add_parser('run-pattern', help='Run individual tests using a pattern.', parents=[run_test_common_parser], usage=run_pattern_usage_text)
513522
run_test_pattern_parser.add_argument('ds', help='Comma separated list of Datasource names or groups to test. See the \'list\' command.', nargs='+')
@@ -540,6 +549,14 @@ def register_tdvt_dialect():
540549
custom_dialect.skipinitialspace = True
541550
csv.register_dialect('tdvt', custom_dialect)
542551

552+
def register_perflab_dialect():
553+
custom_dialect = csv.excel
554+
custom_dialect.lineterminator = '\n'
555+
custom_dialect.delimiter = '|'
556+
custom_dialect.strict = True
557+
custom_dialect.skipinitialspace = True
558+
csv.register_dialect('perflab', custom_dialect)
559+
543560
def check_if_custom_output_dir_exists(custom_output_dir: str) -> bool:
544561
return Path(custom_output_dir).is_dir()
545562

@@ -576,6 +593,7 @@ def init():
576593
ds_reg = get_datasource_registry(sys.platform)
577594
configure_tabquery_path()
578595
register_tdvt_dialect()
596+
register_perflab_dialect()
579597

580598
return parser, ds_reg, args
581599

@@ -590,7 +608,7 @@ def active_thread_count(threads):
590608
return active
591609

592610

593-
def test_runner(all_tests, test_queue, max_threads):
611+
def test_runner(all_tests: List[TestRunner], test_queue: queue.Queue, max_threads: int) -> Tuple[int, int, int, int]:
594612
for i in range(0, max_threads):
595613
worker = threading.Thread(target=do_test_queue_work, args=(i, test_queue))
596614
worker.setDaemon(True)
@@ -607,11 +625,17 @@ def test_runner(all_tests, test_queue, max_threads):
607625
skipped_tests += work.skipped_tests if work.skipped_tests else 0
608626
disabled_tests += work.disabled_tests if work.disabled_tests else 0
609627
total_tests += work.total_tests if work.total_tests else 0
610-
TestOutputFiles.write_test_results_csv(work.test_config.custom_output_dir)
628+
is_perf_run = all_tests[0].test_config.run_as_perf
629+
custom_output_dir = all_tests[0].test_config.custom_output_dir
630+
TestOutputFiles.write_test_results_csv(is_perf_run, custom_output_dir)
611631
return failed_tests, skipped_tests, disabled_tests, total_tests
612632

613633

614-
def run_tests_impl(tests: List[Tuple[TestSet, TestConfig]], max_threads: int, args) -> Optional[Tuple[int, int, int, int]]:
634+
def run_tests_impl(
635+
tests: List[Tuple[TestSet, TdvtInvocation]],
636+
max_threads: int,
637+
args
638+
) -> Optional[Tuple[int, int, int, int]]:
615639
if not tests:
616640
print("No tests found. Check arguments.")
617641
sys.exit()
@@ -661,7 +685,8 @@ def run_tests_impl(tests: List[Tuple[TestSet, TestConfig]], max_threads: int, ar
661685
print("Starting smoke tests. Creating", str(smoke_test_threads), "worker threads.\n")
662686

663687
failed_smoke_tests, skipped_smoke_tests, disabled_smoke_tests, total_smoke_tests = test_runner(
664-
smoke_tests, smoke_test_queue, smoke_test_threads)
688+
smoke_tests, smoke_test_queue, smoke_test_threads
689+
)
665690

666691
smoke_tests_run = total_smoke_tests - disabled_smoke_tests
667692

@@ -727,7 +752,7 @@ def get_ds_list(ds):
727752
ds_list = [x.strip() for x in ds_list]
728753
return ds_list
729754

730-
def run_desired_tests(args, ds_registry):
755+
def run_desired_tests(args, ds_registry: TestRegistry):
731756
generate_files(ds_registry, False)
732757
ds_to_run = ds_registry.get_datasources(get_ds_list(args.ds))
733758
if not ds_to_run:
@@ -744,7 +769,7 @@ def run_desired_tests(args, ds_registry):
744769
sys.exit(0)
745770

746771
max_threads = get_level_of_parallelization(args)
747-
test_sets: List[TestSet] = []
772+
test_sets: List[Tuple[TestSet, TestConfig], Tuple[TestSet, TdvtInvocation]] = []
748773

749774
for ds in ds_to_run:
750775
ds_info = ds_registry.get_datasource_info(ds)

0 commit comments

Comments
 (0)