Revision control

++ #!/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)