diff options
author | Andreas Buhr <andreas.buhr@qt.io> | 2020-10-09 00:02:00 +0200 |
---|---|---|
committer | Andreas Buhr <andreas.buhr@qt.io> | 2020-11-09 16:01:33 +0100 |
commit | 223e409efbf29f7c06bec7a403403c9689f42b3e (patch) | |
tree | 005c43d0ac9d33a74af85e5e3e9c0277b19dadd9 /tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator | |
parent | 3553f8119b5cd8c04c7208c519bb08245a44720e (diff) |
Automatically generate unit tests for QtConcurrent
There are many different ways to to call map and filter functions
in QtConcurrent. This patch adds a python script to generate
all possible combinations.
Change-Id: I61ed1758601e219c5852e8cc939c5feebb23d2f6
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
Diffstat (limited to 'tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator')
7 files changed, 1140 insertions, 0 deletions
diff --git a/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/function_signature.py b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/function_signature.py new file mode 100644 index 0000000000..049eea8d43 --- /dev/null +++ b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/function_signature.py @@ -0,0 +1,163 @@ +############################################################################# +# +# Copyright (C) 2020 The Qt Company Ltd. +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the test suite of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:GPL-EXCEPT$ +# Commercial License Usage +# Licensees holding valid commercial Qt licenses may use this file in +# accordance with the commercial license agreement provided with the +# Software or, alternatively, in accordance with the terms contained in +# a written agreement between you and The Qt Company. For licensing terms +# and conditions see https://www.qt.io/terms-conditions. For further +# information use the contact form at https://www.qt.io/contact-us. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 3 as published by the Free Software +# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +############################################################################# + + +from option_management import need_separate_output_sequence + + +# ["hallo", "welt"] -> "halloWelt" +def build_camel_case(*args): + assert all(isinstance(x, str) for x in args) + if len(args) == 0: + return "" + if len(args) == 1: + return args[0] + return args[0] + "".join(x.capitalize() for x in args[1:]) + + +def build_function_name(options): + result = [] + for part in ["blocking", "filter", "map", "reduce"]: + if options[part]: + result.append(part) + + def make_potentially_passive(verb): + if verb == "map": + return "mapped" + if verb == "blocking": + return "blocking" + + if verb[-1] == "e": + verb += "d" + else: + verb += "ed" + + return verb + + if not options["inplace"]: + result = [make_potentially_passive(x) for x in result] + + result = build_camel_case(*result) + return result + + +def build_blocking_return_type(options): + if options["inplace"]: + if options["filter"] and options["iterators"] and not options["reduce"]: + return "Iterator" # have to mark new end + else: + return "void" + else: + if options["reduce"]: + return "ResultType" + + if need_separate_output_sequence(options): + return "OutputSequence" + else: + return "Sequence" + + +def build_return_type(options): + if options["blocking"]: + return build_blocking_return_type(options) + else: + return f"QFuture<{build_blocking_return_type(options)}>" + + +def build_template_argument_list(options): + required_types = [] + if options["reduce"]: + required_types.append("typename ResultType") + + need_output_sequence = need_separate_output_sequence(options) + if need_output_sequence: + required_types.append("OutputSequence") + + if options["iterators"]: + required_types.append("Iterator") + else: + if need_output_sequence: + required_types.append("InputSequence") + else: + required_types.append("Sequence") + + # Functors + if options["filter"]: + required_types.append("KeepFunctor") + + if options["map"]: + required_types.append("MapFunctor") + + if options["reduce"]: + required_types.append("ReduceFunctor") + + if options["initialvalue"]: + required_types.append("reductionitemtype") + + return "template<" + ", ".join(["typename "+x for x in required_types]) + ">" + + +def build_argument_list(options): + required_arguments = [] + if options["pool"]: + required_arguments.append("QThreadPool* pool") + + if options["iterators"]: + required_arguments.append("Iterator begin") + required_arguments.append("Iterator end") + else: + if options["inplace"]: + required_arguments.append("Sequence & sequence") + else: + if need_separate_output_sequence(options): + required_arguments.append("InputSequence && sequence") + else: + required_arguments.append("const Sequence & sequence") + + if options["filter"]: + required_arguments.append("KeepFunctor filterFunction") + + if options["map"]: + required_arguments.append("MapFunctor function") + + if options["reduce"]: + required_arguments.append("ReduceFunctor reduceFunction") + if options["initialvalue"]: + required_arguments.append("reductionitemtype && initialValue") + + required_arguments.append("ReduceOptions") + + return "(" + ", ".join(required_arguments) + ")" + + +def build_function_signature(options): + return (build_template_argument_list(options) + "\n" + + build_return_type( + options) + " " + build_function_name(options) + build_argument_list(options) + + ";" + ) diff --git a/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generate_excel.py b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generate_excel.py new file mode 100644 index 0000000000..b9aad29e5a --- /dev/null +++ b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generate_excel.py @@ -0,0 +1,71 @@ +############################################################################# +# +# Copyright (C) 2020 The Qt Company Ltd. +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the test suite of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:GPL-EXCEPT$ +# Commercial License Usage +# Licensees holding valid commercial Qt licenses may use this file in +# accordance with the commercial license agreement provided with the +# Software or, alternatively, in accordance with the terms contained in +# a written agreement between you and The Qt Company. For licensing terms +# and conditions see https://www.qt.io/terms-conditions. For further +# information use the contact form at https://www.qt.io/contact-us. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 3 as published by the Free Software +# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +############################################################################# + +import pandas as pd +from option_management import function_describing_options +from function_signature import build_function_signature + + +def generate_excel_file_of_functions(filename): + olist = [] + for options in function_describing_options(): + # filter out unneccesary cases: + if options["reduce"] and options["inplace"]: + # we cannot do a reduction in-place + options["comment"] = "reduce-inplace:nonsense" + options["signature"] = "" + + elif options["initialvalue"] and not options["reduce"]: + options["comment"] = "initial-noreduce:nonsense" + options["signature"] = "" + + elif not options["reduce"] and not options["map"] and not options["filter"]: + # no operation at all + options["comment"] = "noop" + options["signature"] = "" + + else: + options["comment"] = "" + if options["map"] and options["filter"]: + options["implemented"] = "no:filter+map" + elif not options["map"] and not options["filter"]: + options["implemented"] = "no:nofilter+nomap" + elif options["inplace"] and options["iterators"] and options["filter"]: + options["implemented"] = "no:inplace+iterator+filter" + else: + options["implemented"] = "yes" + + options["signature"] = build_function_signature(options) + + olist.append(options) + + df = pd.DataFrame(olist) + df.to_excel(filename) + + +generate_excel_file_of_functions("functions.xls") diff --git a/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generate_gui.py b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generate_gui.py new file mode 100644 index 0000000000..050ccb5839 --- /dev/null +++ b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generate_gui.py @@ -0,0 +1,181 @@ +############################################################################# +# +# Copyright (C) 2020 The Qt Company Ltd. +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the test suite of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:GPL-EXCEPT$ +# Commercial License Usage +# Licensees holding valid commercial Qt licenses may use this file in +# accordance with the commercial license agreement provided with the +# Software or, alternatively, in accordance with the terms contained in +# a written agreement between you and The Qt Company. For licensing terms +# and conditions see https://www.qt.io/terms-conditions. For further +# information use the contact form at https://www.qt.io/contact-us. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 3 as published by the Free Software +# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +############################################################################# + +import importlib +import sys +import PySide2 +from PySide2.QtCore import Signal +from PySide2.QtWidgets import QVBoxLayout, QRadioButton, QGroupBox, QWidget, QApplication, QPlainTextEdit, QHBoxLayout + +import generate_testcase +from helpers import insert_testcases_into_file +from option_management import (Option, OptionManager, testcase_describing_options, function_describing_options, + skip_function_description, disabled_testcase_describing_options, + skip_testcase_description) + + +class MyRadioButton(QRadioButton): + def __init__(self, value): + super(MyRadioButton, self).__init__(text=str(value)) + self.value = value + + self.toggled.connect(lambda x: x and self.activated.emit(self.value)) + + activated = Signal(object) + + +class OptionSelector(QGroupBox): + def __init__(self, parent: QWidget, option: Option): + super(OptionSelector, self).__init__(title=option.name, parent=parent) + self.layout = QVBoxLayout() + self.setLayout(self.layout) + + self.radiobuttons = [] + for val in option.possible_options: + rb = MyRadioButton(val) + self.layout.addWidget(rb) + rb.activated.connect(lambda value: self.valueSelected.emit(option.name, value)) + self.radiobuttons.append(rb) + + self.radiobuttons[0].setChecked(True) + + valueSelected = Signal(str, object) + + +class OptionsSelector(QGroupBox): + def __init__(self, parent: QWidget, option_manager: OptionManager): + super(OptionsSelector, self).__init__(title=option_manager.name, parent=parent) + self.vlayout = QVBoxLayout() + self.setLayout(self.vlayout) + self.layout1 = QHBoxLayout() + self.layout2 = QHBoxLayout() + self.layout3 = QHBoxLayout() + self.vlayout.addLayout(self.layout1) + self.vlayout.addLayout(self.layout2) + self.vlayout.addLayout(self.layout3) + self.disabledOptions = [] + + self.selectors = {} + for option in option_manager.options.values(): + os = OptionSelector(parent=self, option=option) + if "type" in option.name: + self.layout2.addWidget(os) + elif "passing" in option.name: + self.layout3.addWidget(os) + else: + self.layout1.addWidget(os) + os.valueSelected.connect(self._handle_slection) + self.selectors[option.name] = os + + self.selectedOptionsDict = {option.name: option.possible_options[0] for option in + option_manager.options.values()} + + def get_current_option_set(self): + return {k: v for k, v in self.selectedOptionsDict.items() if k not in self.disabledOptions} + + def _handle_slection(self, name: str, value: object): + self.selectedOptionsDict[name] = value + self.optionsSelected.emit(self.get_current_option_set()) + + def set_disabled_options(self, options): + self.disabledOptions = options + for name, selector in self.selectors.items(): + if name in self.disabledOptions: + selector.setEnabled(False) + else: + selector.setEnabled(True) + + optionsSelected = Signal(dict) + + +class MainWindow(QWidget): + def __init__(self): + super(MainWindow, self).__init__() + self.layout = QVBoxLayout() + self.setLayout(self.layout) + + self.functionSelector = OptionsSelector(parent=self, option_manager=function_describing_options()) + self.layout.addWidget(self.functionSelector) + self.testcaseSelector = OptionsSelector(parent=self, option_manager=testcase_describing_options()) + self.layout.addWidget(self.testcaseSelector) + + self.plainTextEdit = QPlainTextEdit() + self.plainTextEdit.setReadOnly(True) + self.layout.addWidget(self.plainTextEdit) + self.plainTextEdit.setFont(PySide2.QtGui.QFont("Fira Code", 8)) + + # temp + self.functionSelector.optionsSelected.connect(lambda o: self._handle_function_change()) + self.testcaseSelector.optionsSelected.connect(lambda o: self._handle_testcase_change()) + + self._handle_function_change() + + def _handle_function_change(self): + options = self.functionSelector.get_current_option_set() + if m := skip_function_description(options): + self.plainTextEdit.setPlainText(m) + return + + options_to_disable = disabled_testcase_describing_options(options) + self.testcaseSelector.set_disabled_options(options_to_disable) + + options.update(self.testcaseSelector.get_current_option_set()) + if m := skip_testcase_description(options): + self.plainTextEdit.setPlainText(m) + return + + self._generate_new_testcase() + + def _handle_testcase_change(self): + options = self.functionSelector.get_current_option_set() + options.update(self.testcaseSelector.get_current_option_set()) + if m := skip_testcase_description(options): + self.plainTextEdit.setPlainText(m) + return + + self._generate_new_testcase() + + def _generate_new_testcase(self): + foptions = self.functionSelector.get_current_option_set() + toptions = self.testcaseSelector.get_current_option_set() + importlib.reload(generate_testcase) + testcase = generate_testcase.generate_testcase(foptions, toptions) + self.plainTextEdit.setPlainText(testcase[1]) + filename = "../tst_qtconcurrentfiltermapgenerated.cpp" + insert_testcases_into_file(filename, [testcase]) + filename = "../tst_qtconcurrentfiltermapgenerated.h" + insert_testcases_into_file(filename, [testcase]) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + + m = MainWindow() + m.show() + + app.exec_() diff --git a/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generate_testcase.py b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generate_testcase.py new file mode 100644 index 0000000000..9ba03a31a0 --- /dev/null +++ b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generate_testcase.py @@ -0,0 +1,391 @@ +############################################################################# +# +# Copyright (C) 2020 The Qt Company Ltd. +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the test suite of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:GPL-EXCEPT$ +# Commercial License Usage +# Licensees holding valid commercial Qt licenses may use this file in +# accordance with the commercial license agreement provided with the +# Software or, alternatively, in accordance with the terms contained in +# a written agreement between you and The Qt Company. For licensing terms +# and conditions see https://www.qt.io/terms-conditions. For further +# information use the contact form at https://www.qt.io/contact-us. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 3 as published by the Free Software +# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +############################################################################# +import textwrap +import time +from subprocess import Popen, PIPE + +from function_signature import build_function_signature, build_function_name +from option_management import need_separate_output_sequence, qt_quirk_case + + +def InputSequenceItem(toptions): + if toptions["inputitemtype"] == "standard": + return "SequenceItem<tag_input>" + if toptions["inputitemtype"] == "noconstruct": + return "NoConstructSequenceItem<tag_input>" + if toptions["inputitemtype"] == "moveonly": + return "MoveOnlySequenceItem<tag_input>" + if toptions["inputitemtype"] == "moveonlynoconstruct": + return "MoveOnlyNoConstructSequenceItem<tag_input>" + assert False + + +def InputSequence(toptions): + item = InputSequenceItem(toptions) + if toptions["inputsequence"] == "standard": + return "std::vector<{}>".format(item) + if toptions["inputsequence"] == "moveonly": + return "MoveOnlyVector<{}>".format(item) + assert False + + +def InputSequenceInitializationString(toptions): + t = InputSequenceItem(toptions) + # construct IILE + mystring = ("[](){" + InputSequence(toptions) + " result;\n" + + "\n".join("result.push_back({}({}, true));".format(t, i) for i in range(1, 7)) + + "\n return result; }()") + return mystring + + +def OutputSequenceItem(toptions): + if toptions["map"] and (toptions["inplace"] or toptions["maptype"] == "same"): + return InputSequenceItem(toptions) + + if toptions["map"]: + if toptions["mappeditemtype"] == "standard": + return "SequenceItem<tag_mapped>" + if toptions["mappeditemtype"] == "noconstruct": + return "NoConstructSequenceItem<tag_mapped>" + if toptions["mappeditemtype"] == "moveonly": + return "MoveOnlySequenceItem<tag_mapped>" + if toptions["mappeditemtype"] == "moveonlynoconstruct": + return "MoveOnlyNoConstructSequenceItem<tag_mapped>" + assert(False) + else: + return InputSequenceItem(toptions) + + +def ReducedItem(toptions): + if toptions["reductiontype"] == "same": + return OutputSequenceItem(toptions) + else: + if toptions["reductionitemtype"] == "standard": + return "SequenceItem<tag_reduction>" + if toptions["reductionitemtype"] == "noconstruct": + return "NoConstructSequenceItem<tag_reduction>" + if toptions["reductionitemtype"] == "moveonly": + return "MoveOnlySequenceItem<tag_reduction>" + if toptions["reductionitemtype"] == "moveonlynoconstruct": + return "MoveOnlyNoConstructSequenceItem<tag_reduction>" + assert(False) + + +def OutputSequence(toptions): + item = OutputSequenceItem(toptions) + # quirk of qt: not a QFuture<Sequence> is returned + if qt_quirk_case(toptions): + return "QList<{}>".format(item) + + needs_extra = need_separate_output_sequence(toptions) + if not needs_extra: + return InputSequence(toptions) + + + if toptions["outputsequence"] == "standard": + return "std::vector<{}>".format(item) + if toptions["outputsequence"] == "moveonly": + return "MoveOnlyVector<{}>".format(item) + assert False + + +def resultData(toptions): + result = [1, 2, 3, 4, 5, 6] + if toptions["filter"]: + result = filter(lambda x: x % 2 == 1, result) + if toptions["map"]: + result = map(lambda x: 2 * x, result) + if toptions["reduce"]: + result = sum(result) + return result + + +def OutputSequenceInitializationString(toptions): + t = OutputSequenceItem(toptions) + # construct IILE + mystring = ("[](){" + OutputSequence(toptions) + " result;\n" + + "\n".join("result.push_back({}({}, true));".format(t, i) for i in resultData(toptions)) + + "\n return result; }()") + return mystring + + +def OutputScalarInitializationString(toptions): + result = resultData(toptions) + assert isinstance(result, int) + return ReducedItem(toptions) + "(" + str(result) + ", true)" + + +def FilterInitializationString(toptions): + item = InputSequenceItem(toptions) + if toptions["filterfunction"] == "function": + return "myfilter<{}>".format(item) + if toptions["filterfunction"] == "functor": + return "MyFilter<{}>{{}}".format(item) + if toptions["filterfunction"] == "memberfunction": + return "&{}::isOdd".format(item) + if toptions["filterfunction"] == "lambda": + return "[](const {}& x){{ return myfilter<{}>(x); }}".format(item, item) + if toptions["filterfunction"] == "moveonlyfunctor": + return "MyMoveOnlyFilter<{}>{{}}".format(item) + assert False + + +def MapInitializationString(toptions): + oldtype = InputSequenceItem(toptions) + newtype = OutputSequenceItem(toptions) + if toptions["inplace"]: + assert oldtype == newtype + if toptions["mapfunction"] == "function": + return "myInplaceMap<{}>".format(oldtype) + if toptions["mapfunction"] == "functor": + return "MyInplaceMap<{}>{{}}".format(oldtype) + if toptions["mapfunction"] == "memberfunction": + return "&{}::multiplyByTwo".format(oldtype) + if toptions["mapfunction"] == "lambda": + return "[]({}& x){{ return myInplaceMap<{}>(x); }}".format(oldtype, oldtype) + if toptions["mapfunction"] == "moveonlyfunctor": + return "MyMoveOnlyInplaceMap<{}>{{}}".format(oldtype) + assert False + else: + if toptions["mapfunction"] == "function": + return "myMap<{f},{t}>".format(f=oldtype, t=newtype) + if toptions["mapfunction"] == "functor": + return "MyMap<{f},{t}>{{}}".format(f=oldtype, t=newtype) + if toptions["mapfunction"] == "memberfunction": + return "&{}::multiplyByTwo".format(newtype) + if toptions["mapfunction"] == "lambda": + return "[](const {f}& x){{ return myMap<{f},{t}>(x); }}".format(f=oldtype, t=newtype) + if toptions["mapfunction"] == "moveonlyfunctor": + return "MyMoveOnlyMap<{f},{t}>{{}}".format(f=oldtype, t=newtype) + assert False + + +def ReductionInitializationString(toptions): + elementtype = OutputSequenceItem(toptions) + sumtype = ReducedItem(toptions) + + if toptions["reductionfunction"] == "function": + return "myReduce<{f},{t}>".format(f=elementtype, t=sumtype) + if toptions["reductionfunction"] == "functor": + return "MyReduce<{f},{t}>{{}}".format(f=elementtype, t=sumtype) + if toptions["reductionfunction"] == "lambda": + return "[]({t}& sum, const {f}& x){{ return myReduce<{f},{t}>(sum, x); }}".format(f=elementtype, t=sumtype) + if toptions["reductionfunction"] == "moveonlyfunctor": + return "MyMoveOnlyReduce<{f},{t}>{{}}".format(f=elementtype, t=sumtype) + assert False + + +def ReductionInitialvalueInitializationString(options): + return ReducedItem(options) + "(0, true)" + + +def function_template_args(options): + args = [] + out_s = OutputSequence(options) + in_s = InputSequence(options) + if options["reduce"] and options["reductionfunction"] == "lambda": + args.append(ReducedItem(options)) + elif out_s != in_s: + if not qt_quirk_case(options): + args.append(out_s) + + if len(args): + return "<" + ", ".join(args) + ">" + + return "" + + +numcall = 0 + + +def generate_testcase(function_options, testcase_options): + options = {**function_options, **testcase_options} + + option_description = "\n".join(" {}={}".format( + a, b) for a, b in testcase_options.items()) + option_description = textwrap.indent(option_description, " "*12) + function_signature = textwrap.indent(build_function_signature(function_options), " "*12) + testcase_name = "_".join("{}_{}".format(x, y) for x, y in options.items()) + global numcall + numcall += 1 + testcase_name = "test" + str(numcall) + function_name = build_function_name(options) + + arguments = [] + + template_args = function_template_args(options) + + # care about the thread pool + if options["pool"]: + pool_initialization = """QThreadPool pool; + pool.setMaxThreadCount(1);""" + arguments.append("&pool") + else: + pool_initialization = "" + + # care about the input sequence + input_sequence_initialization_string = InputSequenceInitializationString(options) + + if "inputsequencepassing" in options and options["inputsequencepassing"] == "lvalue" or options["inplace"]: + input_sequence_initialization = "auto input_sequence = " + \ + input_sequence_initialization_string + ";" + arguments.append("input_sequence") + elif "inputsequencepassing" in options and options["inputsequencepassing"] == "rvalue": + input_sequence_initialization = "" + arguments.append(input_sequence_initialization_string) + else: + input_sequence_initialization = "auto input_sequence = " + \ + input_sequence_initialization_string + ";" + arguments.append("input_sequence.begin()") + arguments.append("input_sequence.end()") + + # care about the map: + if options["map"]: + map_initialization_string = MapInitializationString(options) + if options["mapfunctionpassing"] == "lvalue": + map_initialization = "auto map = " + map_initialization_string + ";" + arguments.append("map") + elif options["mapfunctionpassing"] == "rvalue": + map_initialization = "" + arguments.append(map_initialization_string) + else: + assert False + else: + map_initialization = "" + + # care about the filter + if options["filter"]: + filter_initialization_string = FilterInitializationString(options) + if options["filterfunctionpassing"] == "lvalue": + filter_initialization = "auto filter = " + filter_initialization_string + ";" + arguments.append("filter") + elif options["filterfunctionpassing"] == "rvalue": + filter_initialization = "" + arguments.append(filter_initialization_string) + else: + assert (False) + else: + filter_initialization = "" + + reduction_initialvalue_initialization = "" + # care about reduction + if options["reduce"]: + reduction_initialization_expression = ReductionInitializationString(options) + if options["reductionfunctionpassing"] == "lvalue": + reduction_initialization = "auto reductor = " + reduction_initialization_expression + ";" + arguments.append("reductor") + elif options["reductionfunctionpassing"] == "rvalue": + reduction_initialization = "" + arguments.append(reduction_initialization_expression) + else: + assert (False) + + # initialvalue: + if options["initialvalue"]: + reduction_initialvalue_initialization_expression = ReductionInitialvalueInitializationString(options) + if options["reductioninitialvaluepassing"] == "lvalue": + reduction_initialvalue_initialization = "auto initialvalue = " + reduction_initialvalue_initialization_expression + ";" + arguments.append("initialvalue") + elif options["reductioninitialvaluepassing"] == "rvalue": + reduction_initialvalue_initialization = "" + arguments.append(reduction_initialvalue_initialization_expression) + else: + assert (False) + + if options["reductionoptions"] == "UnorderedReduce": + arguments.append("QtConcurrent::UnorderedReduce") + elif options["reductionoptions"] == "OrderedReduce": + arguments.append("QtConcurrent::OrderedReduce") + elif options["reductionoptions"] == "SequentialReduce": + arguments.append("QtConcurrent::SequentialReduce") + else: + assert options["reductionoptions"] == "unspecified" + else: + reduction_initialization = "" + + # what is the expected result + if options["filter"]: + if not options["reduce"]: + expected_result_expression = OutputSequenceInitializationString(options) + else: + expected_result_expression = OutputScalarInitializationString(options) + elif options["map"]: + if not options["reduce"]: + expected_result_expression = OutputSequenceInitializationString(options) + else: + expected_result_expression = OutputScalarInitializationString(options) + + wait_result_expression = "" + if options["inplace"]: + if options["blocking"]: + result_accepting = "" + result_variable = "input_sequence" + else: + result_accepting = "auto future = " + result_variable = "input_sequence" + wait_result_expression = "future.waitForFinished();" + elif options["blocking"]: + result_accepting = "auto result = " + result_variable = "result" + else: + if not options["reduce"]: + result_accepting = "auto result = " + result_variable = "result.results()" + else: + result_accepting = "auto result = " + result_variable = "result.takeResult()" + + arguments_passing = ", ".join(arguments) + final_string = f""" + void tst_QtConcurrentFilterMapGenerated::{testcase_name}() + {{ + /* test for +{function_signature} + + with +{option_description} + */ + + {pool_initialization} + {input_sequence_initialization} + {filter_initialization} + {map_initialization} + {reduction_initialization} + {reduction_initialvalue_initialization} + + {result_accepting}QtConcurrent::{function_name}{template_args}({arguments_passing}); + + auto expected_result = {expected_result_expression}; + {wait_result_expression} + QCOMPARE({result_variable}, expected_result); + }} + """ + p = Popen(["clang-format"], stdin=PIPE, stdout=PIPE, stderr=PIPE) + final_string = p.communicate(final_string.encode())[0].decode() + + return (f" void {testcase_name}();\n", final_string) diff --git a/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generator_main.py b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generator_main.py new file mode 100644 index 0000000000..399488bb59 --- /dev/null +++ b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/generator_main.py @@ -0,0 +1,58 @@ +############################################################################# +# +# Copyright (C) 2020 The Qt Company Ltd. +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the test suite of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:GPL-EXCEPT$ +# Commercial License Usage +# Licensees holding valid commercial Qt licenses may use this file in +# accordance with the commercial license agreement provided with the +# Software or, alternatively, in accordance with the terms contained in +# a written agreement between you and The Qt Company. For licensing terms +# and conditions see https://www.qt.io/terms-conditions. For further +# information use the contact form at https://www.qt.io/contact-us. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 3 as published by the Free Software +# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +############################################################################# + +from option_management import function_describing_options, skip_function_description, testcase_describing_options +from generate_testcase import generate_testcase +from helpers import insert_testcases_into_file +filename = "../tst_qtconcurrentfiltermapgenerated.cpp" + +testcases = [] +counter = 0 +for fo in function_describing_options(): + if skip_function_description(fo): + continue + + if not ( + fo["blocking"] + and fo["filter"] + # and not fo["map"] + and fo["reduce"] + and not fo["inplace"] + and not fo["iterators"] + and not fo["initialvalue"] + and not fo["pool"] + ): + continue + + for to in testcase_describing_options(fo): + print("generate test") + testcases.append(generate_testcase(fo, to)) + counter += 1 + +print(counter) +insert_testcases_into_file(filename, testcases) diff --git a/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/helpers.py b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/helpers.py new file mode 100644 index 0000000000..ed485ac3da --- /dev/null +++ b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/helpers.py @@ -0,0 +1,56 @@ +############################################################################# +# +# Copyright (C) 2020 The Qt Company Ltd. +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the test suite of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:GPL-EXCEPT$ +# Commercial License Usage +# Licensees holding valid commercial Qt licenses may use this file in +# accordance with the commercial license agreement provided with the +# Software or, alternatively, in accordance with the terms contained in +# a written agreement between you and The Qt Company. For licensing terms +# and conditions see https://www.qt.io/terms-conditions. For further +# information use the contact form at https://www.qt.io/contact-us. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 3 as published by the Free Software +# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +############################################################################# + + +def insert_testcases_into_file(filename, testcases): + # assume testcases is an array of tuples of (declaration, definition) + with open(filename) as f: + inputlines = f.readlines() + outputlines = [] + skipping = False + for line in inputlines: + if not skipping: + outputlines.append(line) + else: + if "END_GENERATED" in line: + outputlines.append(line) + skipping = False + + if "START_GENERATED_SLOTS" in line: + # put in testcases + outputlines += [t[0] for t in testcases] + skipping = True + + if "START_GENERATED_IMPLEMENTATIONS" in line: + # put in testcases + outputlines += [t[1] for t in testcases] + skipping = True + + if outputlines != inputlines: + with open(filename, "w") as f: + f.writelines(outputlines) diff --git a/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/option_management.py b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/option_management.py new file mode 100644 index 0000000000..bac2a8d368 --- /dev/null +++ b/tests/auto/concurrent/qtconcurrentfiltermapgenerated/generator/option_management.py @@ -0,0 +1,220 @@ +############################################################################# +# +# Copyright (C) 2020 The Qt Company Ltd. +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the test suite of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:GPL-EXCEPT$ +# Commercial License Usage +# Licensees holding valid commercial Qt licenses may use this file in +# accordance with the commercial license agreement provided with the +# Software or, alternatively, in accordance with the terms contained in +# a written agreement between you and The Qt Company. For licensing terms +# and conditions see https://www.qt.io/terms-conditions. For further +# information use the contact form at https://www.qt.io/contact-us. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 3 as published by the Free Software +# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +############################################################################# + +import itertools + + +class Option: + def __init__(self, name, possible_options): + self.name = name + self.possible_options = possible_options + + +class OptionManager: + def __init__(self, name): + self.options = {} + self.name = name + + def add_option(self, option: Option): + self.options[option.name] = option + + def iterate(self): + for x in itertools.product(*[ + [(name, x) for x in self.options[name]] + for name in self.options.keys() + ]): + yield dict(x) + + +def function_describing_options(): + om = OptionManager("function options") + + om.add_option(Option("blocking", [True, False])) + om.add_option(Option("filter", [True, False])) + om.add_option(Option("map", [False, True])) + om.add_option(Option("reduce", [False, True])) + om.add_option(Option("inplace", [True, False])) + om.add_option(Option("iterators", [False, True])) + om.add_option(Option("initialvalue", [False, True])) + om.add_option(Option("pool", [True, False])) + + return om + + +def skip_function_description(options): + if options["reduce"] and options["inplace"]: + return "we cannot do a reduction in-place" + + if options["initialvalue"] and not options["reduce"]: + return "without reduction, we do not need an initial value" + + if not options["reduce"] and not options["map"] and not options["filter"]: + return "no operation at all" + + # the following things are skipped because Qt does not support them + if options["filter"] and options["map"]: + return "Not supported by Qt: both map and filter operation" + + if not options["filter"] and not options["map"]: + return "Not supported by Qt: no map and no filter operation" + + if options["inplace"] and options["iterators"] and options["filter"]: + return "Not supported by Qt: filter operation in-place with iterators" + + return False + + +def qt_quirk_case(options): + # whenever a function should return a QFuture<Sequence>, + # it returns a QFuture<item> instead + if options["inplace"] or options["reduce"] or options["blocking"]: + return False + + return True + + +def need_separate_output_sequence(options): + # do we need an output sequence? + if not (options["inplace"] or options["reduce"]): + # transforming a sequence into a sequence + if options["iterators"] or options["map"]: + return True + + return False + + +def testcase_describing_options(): + om = OptionManager("testcase options") + + om.add_option(Option("inputsequence", ["standard", "moveonly"])) + om.add_option(Option("inputsequencepassing", ["lvalue", "rvalue"])) + om.add_option(Option("inputitemtype", ["standard", "noconstruct", "moveonly", "moveonlynoconstruct"])) + + om.add_option(Option("outputsequence", ["standard", "moveonly"])) + + om.add_option(Option("maptype", ["same", "different"])) + om.add_option(Option("mappeditemtype", ["standard", "noconstruct", "moveonly", "moveonlynoconstruct"])) + + om.add_option(Option("reductiontype", ["same", "different"])) + + om.add_option(Option("reductionitemtype", [ + "standard", "noconstruct", "moveonly", "moveonlynoconstruct"])) + + om.add_option(Option("filterfunction", ["functor", "function", "memberfunction", "lambda", "moveonlyfunctor"])) + om.add_option(Option("filterfunctionpassing", ["lvalue", "rvalue"])) + + om.add_option(Option("mapfunction", ["functor", "function", "memberfunction", "lambda", "moveonlyfunctor"])) + om.add_option(Option("mapfunctionpassing", ["lvalue", "rvalue"])) + + om.add_option(Option("reductionfunction", ["functor", "function", "lambda", "moveonlyfunctor"])) + om.add_option(Option("reductionfunctionpassing", ["lvalue", "rvalue"])) + + om.add_option(Option("reductioninitialvaluepassing", ["lvalue", "rvalue"])) + + om.add_option(Option("reductionoptions", [ + "unspecified", "UnorderedReduce", "OrderedReduce", "SequentialReduce"])) + + return om + + +def disabled_testcase_describing_options(options): + disabled_options = [] + + if options["inplace"] or options["iterators"]: + disabled_options.append("inputsequencepassing") + + if not need_separate_output_sequence(options): + disabled_options.append("outputsequence") + + if not options["map"]: + disabled_options.append("mappeditemtype") + + if options["map"] and options["inplace"]: + disabled_options.append("mappeditemtype") + + if not options["filter"]: + disabled_options.append("filterfunction") + disabled_options.append("filterfunctionpassing") + + if not options["map"]: + disabled_options.append("mapfunction") + disabled_options.append("mapfunctionpassing") + + if not options["reduce"]: + disabled_options.append("reductionfunction") + disabled_options.append("reductionfunctionpassing") + + if not options["reduce"]: + disabled_options.append("reductiontype") + disabled_options.append("reductioninitialvaluepassing") + disabled_options.append("reductionoptions") + disabled_options.append("reductionitemtype") + + if not options["initialvalue"]: + disabled_options.append("reductioninitialvaluepassing") + + if not options["map"]: + disabled_options.append("maptype") + else: + if options["inplace"]: + disabled_options.append("maptype") + + return disabled_options + + +def skip_testcase_description(options): + if ( + "maptype" in options and + options["maptype"] == "same" and + "inputitemtype" in options and "mappeditemtype" in options and + (options["inputitemtype"] != options["mappeditemtype"]) + ): + return ("Not possible: map should map to same type, " + "but mapped item type should differ from input item type.") + + if ( + "reductiontype" in options and + options["reductiontype"] == "same"): + # we have to check that this is possible + if ("mappeditemtype" in options and "reductionitemtype" in options + and (options["mappeditemtype"] != options["reductionitemtype"]) + ): + return ("Not possible: should reduce in the same type, " + "but reduction item type should differ from mapped item type.") + if ("mappeditemtype" not in options + and (options["inputitemtype"] != options["reductionitemtype"])): + return ("Not possible: should reduce in the same type, " + "but reduction item type should differ from input item type.") + + if ( + options["map"] and not options["inplace"] + and options["mapfunction"] == "memberfunction" + ): + return "map with memberfunction only available for in-place map" + + return False |