summaryrefslogtreecommitdiffstats
path: root/scripts/packagetesting/testpackage.py
blob: 812c923ef2adbb401caa90420e6dd69f22e20944 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#!/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)