#!/usr/bin/env python3 # Copyright (C) 2019 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 from enum import Enum import os import subprocess import sys import shutil import tempfile desc = """ Qt Package testing script for testing examples of an installed package. Run it with the environment for building (compiler and qtenv.bat on Windows) set up. It will build a selection of examples with console logging enabled and run them. Supported platforms: Linux, Windows (MSVC/MinGW), Windows UWP """ qt_version = (0, 0, 0) qt_mkspec = '' qt_examples_path = '' qt_install_bins = '' make_command = '' PATH = os.environ.get('PATH') deploy_test_path = '' # path with qt_install_bins removed class Deployment(Enum): NO_DEPLOYMENT = 1 """The platform supports deployment, for example Win32, macOS""" DEPLOYMENT_SUPPORTED = 2 """The platform requires deployment, for example WinRT""" DEPLOYMENT_REQUIRED = 3 def deployment(): """Returns whether the platform requires/supports deployment""" if qt_mkspec.startswith('winrt'): return Deployment.DEPLOYMENT_REQUIRED if qt_mkspec.startswith('win32'): return Deployment.DEPLOYMENT_SUPPORTED return Deployment.NO_DEPLOYMENT def deploy_tool_command(binary): """Returns the command to deploy an example""" if qt_mkspec.startswith('win32') or qt_mkspec.startswith('winrt'): return ['windeployqt', '--no-translations', binary] return [] def example_command(binary): """Returns the command to launch an example""" if qt_mkspec.startswith('winrt'): return ['winrtrunner', '--profile', 'appx', '--device', '0', '--wait', '0', '--start', binary] return [binary] def normalize_path(p): return os.path.normcase(os.path.normpath(p)) def build_deploy_test_path(): """Build a path with qt_install_bins removed for testing the deployed binary""" path_sep = ':' if sys.platform != 'win32' else ';' result = [] for p in PATH.split(path_sep): if normalize_path(p) != qt_install_bins: result.append(p) return path_sep.join(result) def qt_version_less_than(major, minor, patch): return qt_version < (major, minor, patch) def qt_version_greater_equal_than(major, minor, patch): return qt_version >= (major, minor, patch) def examples(): """Compile a list of examples to be tested""" global qt_mkspec result = ['widgets/mainwindows/mdi', 'quickcontrols2/gallery'] if qt_version_less_than(6, 0, 0): if not qt_mkspec.startswith('winrt'): result.append('sensors/sensor_explorer') result.extend(['charts/qmlchart', 'multimedia/declarative-camera', 'location/mapviewer', 'quickcontrols/extras/gallery']) if not qt_mkspec.startswith('winrt') and qt_mkspec != 'win32-g++': result.append('webengine/quicknanobrowser') return result def determine_make_command(mkspec): """Determine the make call (silent to emphasize warnings)""" if mkspec == 'win32-g++': return ['mingw32-make', '-s'] if mkspec.startswith('win'): return ['nmake', '/s', '/l'] return ['make', '-s'] def query_qmake(): """Run a qmake query to obtain version, mkspec and path""" global make_command, qt_examples_path, qt_install_bins, qt_mkspec global qt_version for line in run_process_output(['qmake', '-query']): print_line = True if line.startswith('QMAKE_XSPEC:'): qt_mkspec = line[12:] elif line.startswith('QT_VERSION:'): qt_version = tuple(int(v) for v in line[11:].split('.')) elif line.startswith('QT_INSTALL_EXAMPLES:'): qt_examples_path = normalize_path(line[20:]) elif line.startswith('QT_INSTALL_BINS:'): qt_install_bins = normalize_path(line[16:]) else: print_line = False if print_line: print(line) make_command = determine_make_command(qt_mkspec) def execute(args): """Execute a command and print output""" log_string = '[{}] {}'.format(os.path.basename(os.getcwd()), ' '.join(args)) print(log_string) exit_code = subprocess.call(args) if exit_code != 0: raise RuntimeError('FAIL({}): {}'.format(exit_code, log_string)) def run_process_output(args): """Execute a command and capture stdout""" std_out = subprocess.Popen(args, universal_newlines=1, stdout=subprocess.PIPE).stdout result = [line.rstrip() for line in std_out.readlines()] std_out.close() return result def build_qmake(example_src_path): execute(['qmake', 'CONFIG+=console', example_src_path]) execute(make_command) def build_cmake(example_src_path): execute(['cmake', '-H' + example_src_path, '-B.', '-G', 'Ninja', '-DCMAKE_BUILD_TYPE=Release']) execute(['cmake', '--build', '.', '--parallel']) def run_example(example, test_deployment): """Build and run an example""" global qt_mkspec name = os.path.basename(example) # Disambiguate identical directory names of for example QQC1/2 'gallery' dir_name = name while os.path.exists(dir_name): dir_name += '_1' result = False print('#### Running {} #####'.format(name)) os.mkdir(dir_name) os.chdir(dir_name) try: example_src_path = os.path.join(qt_examples_path, example) if qt_version_greater_equal_than(6, 0, 0): build_cmake(example_src_path) else: build_qmake(example_src_path) binary = name if name == 'mapviewer': binary = 'qml_location_mapviewer' elif qt_version_greater_equal_than(6, 0, 0) and name == 'gallery': binary = 'gallery_controls2' if qt_mkspec.startswith('win'): binary += '.exe' # Note: After QTBUG-78445, MinGW has release/debug folders only # up to 5.14 and sensor_explorer has none if not os.path.exists(binary): binary = os.path.join('release', binary) binary = os.path.join(os.getcwd(), binary) do_deploy = (deployment() == Deployment.DEPLOYMENT_REQUIRED or (test_deployment and deployment() != Deployment.NO_DEPLOYMENT)) if do_deploy: execute(deploy_tool_command(binary)) os.environ['PATH'] = deploy_test_path execute(example_command(binary)) result = True print('#### ok {} #####'.format(name)) except Exception as e: print('#### FAIL {} #####'.format(name), e) finally: os.environ['PATH'] = PATH os.chdir('..') return result if __name__ == "__main__": if sys.version_info[0] < 3: raise Exception('This script requires Python 3') query_qmake() deploy_test_path = build_deploy_test_path() print('#### Found Qt {}.{}.{}, "{}", examples at {}'.format( qt_version[0], qt_version[1], qt_version[2], qt_mkspec, qt_examples_path)) if qt_version[0] < 5 or not qt_mkspec or not qt_examples_path: print('No suitable Qt version could be found') sys.exit(1) current_dir = os.getcwd() temp_dir = tempfile.mkdtemp(prefix='qtpkgtest{}{}{}'.format( qt_version[0], qt_version[1], qt_version[2])) os.chdir(temp_dir) error_count = 0 for index, example in enumerate(examples()): if not run_example(example, index == 0): error_count = error_count + 1 os.chdir(current_dir) shutil.rmtree(temp_dir) print('#### Done ({} errors) #####'.format(error_count)) sys.exit(error_count)