Skip to content

Commit acaad3b

Browse files
Merge pull request #197 from firebase/feature/aks-improve-logging
Improve logging in integration test workflows
2 parents 60d5aab + 5d37358 commit acaad3b

File tree

7 files changed

+321
-118
lines changed

7 files changed

+321
-118
lines changed

.github/workflows/cpp-packaging.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,3 +677,7 @@ jobs:
677677
if: matrix.target_platform != 'Desktop' && !cancelled()
678678
run: |
679679
python scripts/gha/test_lab.py --android_model ${{ env.android_device }} --android_api ${{ env.android_api }} --ios_model ${{ env.ios_device }} --ios_version ${{ env.ios_version }} --testapp_dir testapps --code_platform cpp --key_file scripts/gha-encrypted/gcs_key_file.json
680+
- name: Summarize build and test results
681+
if: ${{ !cancelled() }}
682+
shell: bash
683+
run: cat testapps/summary.log

.github/workflows/integration_tests.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,14 @@ jobs:
116116
pip install -r scripts/gha/requirements.txt
117117
python scripts/gha/restore_secrets.py --passphrase "${{ secrets.TEST_SECRET }}"
118118
119-
- name: Build integration tests (and run Desktop tests)
119+
- name: Build integration tests
120120
run: |
121-
python scripts/gha/build_testapps.py --t ${{ github.event.inputs.apis }} --p ${{ matrix.target_platform }} --output_directory ${{ github.workspace }} --use_vcpkg --execute_desktop_testapp --noadd_timestamp
121+
python scripts/gha/build_testapps.py --t ${{ github.event.inputs.apis }} --p ${{ matrix.target_platform }} --output_directory ${{ github.workspace }} --use_vcpkg --noadd_timestamp
122122
123+
- name: Run desktop integration tests
124+
if: matrix.target_platform == 'Desktop' && !cancelled()
125+
run: |
126+
python scripts/gha/desktop_tester.py --testapp_dir testapps
123127
# Workaround for https://github.com/GoogleCloudPlatform/github-actions/issues/100
124128
# Must be run after the Python setup action
125129
- name: Set CLOUDSDK_PYTHON (Windows)
@@ -137,3 +141,7 @@ jobs:
137141
if: matrix.target_platform != 'Desktop' && !cancelled()
138142
run: |
139143
python scripts/gha/test_lab.py --android_model ${{ github.event.inputs.android_device }} --android_api ${{ github.event.inputs.android_api }} --ios_model ${{ github.event.inputs.ios_device }} --ios_version ${{ github.event.inputs.ios_version }} --testapp_dir ${{ github.workspace }}/testapps --code_platform cpp --key_file ${{ github.workspace }}/scripts/gha-encrypted/gcs_key_file.json
144+
- name: Summarize build and test results
145+
if: ${{ !cancelled() }}
146+
shell: bash
147+
run: cat testapps/summary.log

scripts/gha/build_testapps.py

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import attr
8282

8383
from integration_testing import config_reader
84+
from integration_testing import test_validation
8485
from integration_testing import xcodebuild
8586
import utils
8687

@@ -145,11 +146,6 @@
145146
" the local spec repos available on this machine. Must also include iOS"
146147
" in platforms flag.")
147148

148-
flags.DEFINE_bool(
149-
"execute_desktop_testapp", True,
150-
"(Desktop only) Run the testapp after building it. Will return non-zero"
151-
" code if any tests fail inside the testapp.")
152-
153149
flags.DEFINE_string(
154150
"compiler", None,
155151
"(Desktop only) Specify the compiler with CMake during the testapps build."
@@ -187,6 +183,7 @@ def main(argv):
187183
timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S")
188184
else:
189185
timestamp = ""
186+
output_dir = os.path.join(output_dir, "testapps" + timestamp)
190187

191188
ios_framework_dir = os.path.join(sdk_dir, "frameworks")
192189
ios_framework_exist = os.path.isdir(ios_framework_dir)
@@ -218,25 +215,22 @@ def main(argv):
218215
output_dir=output_dir,
219216
sdk_dir=sdk_dir,
220217
ios_framework_exist=ios_framework_exist,
221-
timestamp=timestamp,
222218
root_dir=root_dir,
223219
ios_sdk=FLAGS.ios_sdk,
224-
cmake_flags=cmake_flags,
225-
execute_desktop_testapp=FLAGS.execute_desktop_testapp)
220+
cmake_flags=cmake_flags)
226221
logging.info("END building for %s", testapp)
227222

228-
_summarize_results(testapps, platforms, failures)
223+
_summarize_results(testapps, platforms, failures, output_dir)
229224
return 1 if failures else 0
230225

231226

232227
def _build(
233228
testapp, platforms, api_config, output_dir, sdk_dir, ios_framework_exist,
234-
timestamp, root_dir, ios_sdk, cmake_flags, execute_desktop_testapp):
229+
root_dir, ios_sdk, cmake_flags):
235230
"""Builds one testapp on each of the specified platforms."""
236231
testapp_dir = os.path.join(root_dir, api_config.testapp_path)
237232
project_dir = os.path.join(
238-
output_dir, "testapps" + timestamp, api_config.full_name,
239-
os.path.basename(testapp_dir))
233+
output_dir, api_config.full_name, os.path.basename(testapp_dir))
240234

241235
logging.info("Copying testapp project to %s", project_dir)
242236
os.makedirs(project_dir)
@@ -253,8 +247,6 @@ def _build(
253247
logging.info("BEGIN %s, %s", testapp, _DESKTOP)
254248
try:
255249
_build_desktop(sdk_dir, cmake_flags)
256-
if execute_desktop_testapp:
257-
_execute_desktop_testapp(project_dir)
258250
except subprocess.SubprocessError as e:
259251
failures.append(
260252
Failure(testapp=testapp, platform=_DESKTOP, error_message=str(e)))
@@ -291,36 +283,30 @@ def _build(
291283
return failures
292284

293285

294-
def _summarize_results(testapps, platforms, failures):
286+
def _summarize_results(testapps, platforms, failures, output_dir):
295287
"""Logs a readable summary of the results of the build."""
296-
logging.info(
297-
"FINISHED BUILDING TESTAPPS.\n\n\n"
298-
"Tried to build these testapps: %s\n"
299-
"On these platforms: %s",
300-
", ".join(testapps), ", ".join(platforms))
288+
summary = []
289+
summary.append("BUILD SUMMARY:")
290+
summary.append("TRIED TO BUILD: " + ",".join(testapps))
291+
summary.append("ON PLATFORMS: " + ",".join(platforms))
292+
301293
if not failures:
302-
logging.info("No failures occurred")
294+
summary.append("ALL BUILDS SUCCEEDED")
303295
else:
304-
# Collect lines, then log once, to reduce logging noise from timestamps etc.
305-
lines = ["Some failures occurred:"]
296+
summary.append("SOME FAILURES OCCURRED:")
306297
for i, failure in enumerate(failures, start=1):
307-
lines.append("%d: %s" % (i, failure.describe()))
308-
logging.info("\n".join(lines))
298+
summary.append("%d: %s" % (i, failure.describe()))
299+
summary = "\n".join(summary)
300+
301+
logging.info(summary)
302+
test_validation.write_summary(output_dir, summary)
309303

310304

311305
def _build_desktop(sdk_dir, cmake_flags):
312306
_run(["cmake", ".", "-DFIREBASE_CPP_SDK_DIR=" + sdk_dir] + cmake_flags)
313307
_run(["cmake", "--build", "."])
314308

315309

316-
def _execute_desktop_testapp(project_dir):
317-
if platform.system() == "Windows":
318-
testapp_path = os.path.join(project_dir, "Debug", "integration_test.exe")
319-
else:
320-
testapp_path = os.path.join(project_dir, "integration_test")
321-
_run([testapp_path], timeout=300)
322-
323-
324310
def _get_desktop_compiler_flags(compiler, compiler_table):
325311
"""Returns the command line flags for this compiler."""
326312
if not compiler: # None is an acceptable default value
@@ -461,14 +447,10 @@ def _build_ios(
461447

462448
podfile_tool_path = os.path.join(
463449
root_dir, "scripts", "gha", "integration_testing", "update_podfile.py")
464-
sdk_podfile_path = os.path.join(
465-
root_dir, "ios_pod", "Podfile")
466-
app_podfile_path = os.path.join(
467-
project_dir, "Podfile")
468450
podfile_patcher_args = [
469451
sys.executable, podfile_tool_path,
470-
"--sdk_podfile", sdk_podfile_path,
471-
"--app_podfile", app_podfile_path
452+
"--sdk_podfile", os.path.join(root_dir, "ios_pod", "Podfile"),
453+
"--app_podfile", os.path.join(project_dir, "Podfile")
472454
]
473455
_run(podfile_patcher_args)
474456
_run(["pod", "install"])

scripts/gha/desktop_tester.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Runs and validates desktop C++ testapps.
16+
17+
Usage:
18+
python desktop_tester.py --testapp_dir ~/Downloads/testapps
19+
20+
This will search --testapp_dir for files whose name is given by --testapp_name
21+
(default: "integration_test", or "integration_test.exe" on Windows), execute
22+
them, parse their log output according to the C++ test summary format, and
23+
report a summary of results.
24+
25+
"""
26+
27+
import os
28+
import platform
29+
import subprocess
30+
import threading
31+
32+
from absl import app
33+
from absl import flags
34+
from absl import logging
35+
import attr
36+
37+
from integration_testing import test_validation
38+
39+
FLAGS = flags.FLAGS
40+
41+
flags.DEFINE_string("testapp_dir", None, "Look for testapps in this directory.")
42+
flags.DEFINE_string("testapp_name", "integration_test", "Name of the testapps.")
43+
44+
45+
def main(argv):
46+
if len(argv) > 1:
47+
raise app.UsageError("Too many command-line arguments.")
48+
49+
testapp_dir = _fix_path(FLAGS.testapp_dir)
50+
51+
testapp_name = FLAGS.testapp_name
52+
if platform.system() == "Windows":
53+
testapp_name += ".exe"
54+
55+
testapps = []
56+
for file_dir, _, file_names in os.walk(testapp_dir):
57+
for file_name in file_names:
58+
if file_name == testapp_name:
59+
testapps.append(os.path.join(file_dir, file_name))
60+
if not testapps:
61+
logging.error("No testapps found.")
62+
test_validation.write_summary(testapp_dir, "Desktop tests: none found.")
63+
return 1
64+
logging.info("Testapps found: %s\n", "\n".join(testapps))
65+
66+
tests = [Test(testapp_path=testapp) for testapp in testapps]
67+
68+
logging.info("Running tests...")
69+
threads = []
70+
for test in tests:
71+
thread = threading.Thread(target=test.run)
72+
threads.append(thread)
73+
thread.start()
74+
for thread in threads:
75+
thread.join()
76+
77+
return test_validation.summarize_test_results(
78+
tests, test_validation.CPP, testapp_dir)
79+
80+
81+
def _fix_path(path):
82+
"""Expands ~, normalizes slashes, and converts relative paths to absolute."""
83+
return os.path.abspath(os.path.expanduser(path))
84+
85+
86+
@attr.s(frozen=False, eq=False)
87+
class Test(object):
88+
"""Holds data related to the testing of one testapp."""
89+
testapp_path = attr.ib()
90+
# This will be populated after the test completes, instead of initialization.
91+
logs = attr.ib(init=False, default=None)
92+
93+
# This runs in a separate thread, so instead of returning values we store
94+
# them as fields so they can be accessed from the main thread.
95+
def run(self):
96+
"""Executes this testapp."""
97+
result = subprocess.run(
98+
args=[self.testapp_path],
99+
cwd=os.path.dirname(self.testapp_path), # Testapp checks CWD for config
100+
stdout=subprocess.PIPE,
101+
stderr=subprocess.STDOUT,
102+
text=True,
103+
check=False,
104+
timeout=300)
105+
logging.info("Finished running %s", self.testapp_path)
106+
107+
self.logs = result.stdout
108+
self.return_code = result.returncode
109+
110+
111+
if __name__ == "__main__":
112+
app.run(main)

scripts/gha/integration_testing/gcs.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ def authorize_gcs(key_file):
8989
check=True)
9090

9191

92+
# This is intended to be logged when a tool stores artifacts on GCS.
93+
def get_gsutil_tips():
94+
"""Returns a human readable string with tips on accessing a GCS bucket."""
95+
return "\n".join((
96+
"GCS Advice: Install the Google Cloud SDK to access the gsutil tool.",
97+
"Use 'gsutil ls <gs_uri>' to list contents of a directory on GCS.",
98+
"Use 'gsutil cp <gs_uri> <local_path>' to copy an artifact.",
99+
"Use 'gsutil -m cp -r <gs_uri> <local_path>' to copy a directory."
100+
))
101+
102+
92103
def _verify_gcloud_sdk_command_line_tools():
93104
"""Verifies the presence of the gCloud SDK's command line tools."""
94105
logging.info("Looking for gcloud and gsutil tools...")

0 commit comments

Comments
 (0)