Skip to content

Commit 6d5fbae

Browse files
authored
Create build matrices & test matrices for integration test workflow (#415)
1 parent bd9bd7f commit 6d5fbae

File tree

13 files changed

+968
-345
lines changed

13 files changed

+968
-345
lines changed

.github/workflows/integration_tests.yml

Lines changed: 647 additions & 158 deletions
Large diffs are not rendered by default.

scripts/gha/build_testapps.py

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@
106106
_SUPPORTED_PLATFORMS = (_ANDROID, _IOS, _DESKTOP)
107107

108108
# Values for iOS SDK flag (where the iOS app will run)
109-
_IOS_SDK_DEVICE = "device"
110-
_IOS_SDK_SIMULATOR = "simulator"
109+
_IOS_SDK_DEVICE = "real"
110+
_IOS_SDK_SIMULATOR = "virtual"
111111
_SUPPORTED_IOS_SDK = (_IOS_SDK_DEVICE, _IOS_SDK_SIMULATOR)
112112

113113
FLAGS = flags.FLAGS
@@ -120,6 +120,12 @@
120120
"output_directory", "~",
121121
"Build output will be placed in this directory.")
122122

123+
flags.DEFINE_string(
124+
"artifact_name", "",
125+
"artifacts will be created and placed in output_directory."
126+
" testapps artifact is testapps-$artifact_name;"
127+
" build log artifact is build-results-$artifact_name.log.")
128+
123129
flags.DEFINE_string(
124130
"repo_dir", os.getcwd(),
125131
"Firebase C++ SDK Git repository. Current directory by default.")
@@ -141,8 +147,8 @@
141147

142148
flags.DEFINE_list(
143149
"ios_sdk", _IOS_SDK_DEVICE,
144-
"(iOS only) Build for device (ipa), simulator (app), or both."
145-
" Building for both will produce both an .app and an .ipa.")
150+
"(iOS only) Build for real device (.ipa), virtual device / simulator (.app), "
151+
"or both. Building for both will produce both an .app and an .ipa.")
146152

147153
flags.DEFINE_bool(
148154
"update_pod_repo", True,
@@ -186,7 +192,7 @@ def main(argv):
186192
testapps = FLAGS.testapps
187193

188194
sdk_dir = _fix_path(FLAGS.packaged_sdk or FLAGS.repo_dir)
189-
output_dir = _fix_path(FLAGS.output_directory)
195+
root_output_dir = _fix_path(FLAGS.output_directory)
190196
repo_dir = _fix_path(FLAGS.repo_dir)
191197

192198
update_pod_repo = FLAGS.update_pod_repo
@@ -196,9 +202,9 @@ def main(argv):
196202
timestamp = ""
197203

198204
if FLAGS.short_output_paths:
199-
output_dir = os.path.join(output_dir, "ta")
205+
output_dir = os.path.join(root_output_dir, "ta")
200206
else:
201-
output_dir = os.path.join(output_dir, "testapps" + timestamp)
207+
output_dir = os.path.join(root_output_dir, "testapps" + timestamp)
202208

203209
ios_framework_dir = os.path.join(sdk_dir, "xcframeworks")
204210
ios_framework_exist = os.path.isdir(ios_framework_dir)
@@ -248,8 +254,10 @@ def main(argv):
248254
cmake_flags=cmake_flags,
249255
short_output_paths=FLAGS.short_output_paths)
250256
logging.info("END building for %s", testapp)
257+
258+
_collect_integration_tests(testapps, root_output_dir, FLAGS.artifact_name)
251259

252-
_summarize_results(testapps, platforms, failures, output_dir)
260+
_summarize_results(testapps, platforms, failures, root_output_dir, FLAGS.artifact_name)
253261
return 1 if failures else 0
254262

255263

@@ -321,8 +329,43 @@ def _build(
321329
return failures
322330

323331

324-
def _summarize_results(testapps, platforms, failures, output_dir):
332+
def _collect_integration_tests(testapps, output_dir, artifact_name):
333+
testapps_artifact_dir = "testapps-" + artifact_name
334+
android_testapp_extension = ".apk"
335+
ios_testapp_extension = ".ipa"
336+
ios_simualtor_testapp_extension = ".app"
337+
desktop_testapp_name = "integration_test"
338+
if platform.system() == "Windows":
339+
desktop_testapp_name += ".exe"
340+
341+
testapp_paths = []
342+
for file_dir, directories, file_names in os.walk(output_dir):
343+
for directory in directories:
344+
if directory.endswith(ios_simualtor_testapp_extension):
345+
testapp_paths.append(os.path.join(file_dir, directory))
346+
for file_name in file_names:
347+
if ((file_name == desktop_testapp_name and "ios_build" not in file_dir)
348+
or file_name.endswith(android_testapp_extension)
349+
or file_name.endswith(ios_testapp_extension)):
350+
testapp_paths.append(os.path.join(file_dir, file_name))
351+
352+
artifact_path = os.path.join(output_dir, testapps_artifact_dir)
353+
for testapp in testapps:
354+
os.makedirs(os.path.join(artifact_path, testapp))
355+
for path in testapp_paths:
356+
for testapp in testapps:
357+
if testapp in path:
358+
if os.path.isfile(path):
359+
shutil.copy(path, os.path.join(artifact_path, testapp))
360+
else:
361+
dir_util.copy_tree(path, os.path.join(artifact_path, testapp, "integration_test.app"))
362+
break
363+
364+
365+
def _summarize_results(testapps, platforms, failures, output_dir, artifact_name):
325366
"""Logs a readable summary of the results of the build."""
367+
file_name = "build-results-" + artifact_name + ".log"
368+
326369
summary = []
327370
summary.append("BUILD SUMMARY:")
328371
summary.append("TRIED TO BUILD: " + ",".join(testapps))
@@ -337,7 +380,7 @@ def _summarize_results(testapps, platforms, failures, output_dir):
337380
summary = "\n".join(summary)
338381

339382
logging.info(summary)
340-
test_validation.write_summary(output_dir, summary)
383+
test_validation.write_summary(output_dir, summary, file_name=file_name)
341384

342385

343386
def _build_desktop(sdk_dir, cmake_flags):

scripts/gha/desktop_tester.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040

4141
flags.DEFINE_string("testapp_dir", None, "Look for testapps in this directory.")
4242
flags.DEFINE_string("testapp_name", "integration_test", "Name of the testapps.")
43+
flags.DEFINE_string(
44+
"logfile_name", "",
45+
"Create test log artifact test-results-$logfile_name.log."
46+
" logfile will be created and placed in testapp_dir.")
4347

4448

4549
def main(argv):
@@ -55,7 +59,8 @@ def main(argv):
5559
testapps = []
5660
for file_dir, _, file_names in os.walk(testapp_dir):
5761
for file_name in file_names:
58-
if file_name == testapp_name:
62+
# ios build create intermediates file with same name, filter them out
63+
if file_name == testapp_name and "ios_build" not in file_dir:
5964
testapps.append(os.path.join(file_dir, file_name))
6065
if not testapps:
6166
logging.error("No testapps found.")
@@ -75,7 +80,10 @@ def main(argv):
7580
thread.join()
7681

7782
return test_validation.summarize_test_results(
78-
tests, test_validation.CPP, testapp_dir)
83+
tests,
84+
test_validation.CPP,
85+
testapp_dir,
86+
file_name="test-results-" + FLAGS.logfile_name + ".log")
7987

8088

8189
def _fix_path(path):
@@ -95,6 +103,7 @@ class Test(object):
95103
def run(self):
96104
"""Executes this testapp."""
97105
result = None # Ensures this var is defined if timeout occurs.
106+
os.chmod(self.testapp_path, 0o777)
98107
try:
99108
result = subprocess.run(
100109
args=[self.testapp_path],

scripts/gha/integration_testing/gameloop/gameloop.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@
294294
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
295295
GCC_WARN_UNUSED_FUNCTION = YES;
296296
GCC_WARN_UNUSED_VARIABLE = YES;
297-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
297+
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
298298
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
299299
MTL_FAST_MATH = YES;
300300
ONLY_ACTIVE_ARCH = YES;
@@ -349,7 +349,7 @@
349349
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
350350
GCC_WARN_UNUSED_FUNCTION = YES;
351351
GCC_WARN_UNUSED_VARIABLE = YES;
352-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
352+
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
353353
MTL_ENABLE_DEBUG_INFO = NO;
354354
MTL_FAST_MATH = YES;
355355
SDKROOT = iphoneos;
@@ -366,7 +366,7 @@
366366
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
367367
CODE_SIGN_STYLE = Automatic;
368368
INFOPLIST_FILE = gameloop/Info.plist;
369-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
369+
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
370370
LD_RUNPATH_SEARCH_PATHS = (
371371
"$(inherited)",
372372
"@executable_path/Frameworks",
@@ -385,7 +385,7 @@
385385
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
386386
CODE_SIGN_STYLE = Automatic;
387387
INFOPLIST_FILE = gameloop/Info.plist;
388-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
388+
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
389389
LD_RUNPATH_SEARCH_PATHS = (
390390
"$(inherited)",
391391
"@executable_path/Frameworks",

scripts/gha/integration_testing/gameloop_android/app/src/main/java/com/google/firebase/gameloop/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
3131
val pkgAppsList: List<ResolveInfo> = packageManager.queryIntentActivities(intent, 0)
3232
val gamePackageName = pkgAppsList[0].activityInfo.packageName
3333

34-
val dir = File(getExternalFilesDir(null), gamePackageName)
34+
val dir = File(getFilesDir(), gamePackageName)
3535
if (!dir.exists()) dir.mkdirs()
3636
val filename = "Results1.json"
3737
val file = File(dir, filename)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<paths>
3-
<external-path
3+
<files-path
44
name="files"
55
path="/"/>
66
</paths>

scripts/gha/integration_testing/test_validation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def validate_results_cpp(log_text):
124124
summary=result_summary)
125125

126126

127-
def summarize_test_results(tests, platform, summary_dir, extra_info=""):
127+
def summarize_test_results(tests, platform, summary_dir, file_name="summary.log", extra_info=""):
128128
"""Summarizes and logs test results for multiple tests.
129129
130130
Each 'test' should be an object with properties "testapp_path", which
@@ -199,12 +199,12 @@ def summarize_test_results(tests, platform, summary_dir, extra_info=""):
199199
% (len(tests), len(successes), len(failures), len(errors)))
200200
summary = "\n".join(summary)
201201
logging.info(summary)
202-
write_summary(summary_dir, summary)
202+
write_summary(summary_dir, summary, file_name)
203203

204204
return 0 if len(tests) == len(successes) else 1
205205

206206

207-
def write_summary(testapp_dir, summary):
207+
def write_summary(testapp_dir, summary, file_name="summary.log"):
208208
"""Writes a summary of tests/builds to a file in the testapp directory.
209209
210210
This will append the given summary to a file in the testapp directory,
@@ -217,7 +217,7 @@ def write_summary(testapp_dir, summary):
217217
218218
"""
219219
# This method serves as the source of truth on where to put the summary.
220-
with open(os.path.join(testapp_dir, "summary.log"), "a") as f:
220+
with open(os.path.join(testapp_dir, file_name), "a") as f:
221221
# The timestamp mainly helps when running locally: if running multiple
222222
# tests on the same directory, the results will accumulate, with a timestamp
223223
# to help keep track of when a given test was run.

scripts/gha/integration_testing/xcodebuild.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def get_args_for_build(
4545
scheme (str): Name of the scheme to build.
4646
output_dir (str): Directory for the resulting build artifacts. Will be
4747
created if it doesn't already exist.
48-
ios_sdk (str): Where this build will be run: device or simulator.
48+
ios_sdk (str): Where this build will be run: real device or virtual device (simulator).
4949
configuration (str): Value for the -configuration flag.
5050
5151
Returns:
@@ -77,16 +77,16 @@ def get_args_for_build(
7777

7878
def _get_ios_env_from_target(ios_sdk):
7979
"""Return a value for the -sdk flag based on the target (device/simulator)."""
80-
if ios_sdk == "device":
80+
if ios_sdk == "real":
8181
return "iphoneos"
82-
elif ios_sdk == "simulator":
82+
elif ios_sdk == "virtual":
8383
return "iphonesimulator"
8484
else:
8585
raise ValueError("Unrecognized ios_sdk: %s" % ios_sdk)
8686

8787

8888
def generate_unsigned_ipa(output_dir, configuration):
89-
"""create unsigned .ipa from .app
89+
"""create unsigned .ipa from .app, then remove .app afterwards
9090
9191
Args:
9292
output_dir (str): Same value as get_args_for_build. generated unsigned .ipa
@@ -101,3 +101,4 @@ def generate_unsigned_ipa(output_dir, configuration):
101101
shutil.move(app_path, payload_path)
102102
shutil.make_archive(payload_path, 'zip', root_dir=iphone_build_dir, base_dir='Payload')
103103
shutil.move('%s.%s'%(payload_path, 'zip'), ipa_path)
104+
shutil.rmtree(payload_path)

scripts/gha/print_matrix_configuration.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,27 @@
9999
"matrix": {
100100
"os": ["ubuntu-latest", "macos-latest", "windows-latest"],
101101
"platform": ["Desktop", "Android", "iOS"],
102-
"ssl_lib": ["openssl", "boringssl"]
102+
"ssl_lib": ["openssl", "boringssl"],
103+
"android_device": ["virtual:system-images;android-28;google_apis;x86_64+28.0.3", "real:flame+29"],
104+
"ios_device": ["virtual:iPhone 8+12.0", "real:iphone8+12.4"],
105+
EXPANDED_KEY: {
106+
"android_device": ["virtual:system-images;android-18;default;x86+28.0.3",
107+
"virtual:system-images;android-28;google_apis;x86_64+28.0.3",
108+
"virtual:system-images;android-29;default;x86+28.0.3",
109+
"real:Nexus10+19",
110+
"real:Pixel2+28",
111+
"real:flame+29"],
112+
"ios_device": ["virtual:iPhone 6+11.4",
113+
"virtual:iPhone 8+12.0",
114+
"virtual:iPhone 11+14.4",
115+
"real:iphone8+11.4",
116+
"real:iphone6s+12.0",
117+
"real:iphone11pro+14.1"]
118+
}
103119
},
104120
"config": {
105121
"apis": "admob,analytics,auth,database,dynamic_links,firestore,functions,installations,messaging,remote_config,storage",
106-
"mobile_test_on": "device,simulator",
107-
"android_device": "flame",
108-
"android_api": "29",
109-
"ios_device": "iphone8",
110-
"ios_version": "12.4"
122+
"mobile_test_on": "real"
111123
}
112124
},
113125

@@ -172,6 +184,18 @@ def get_value(workflow, use_expanded, parm_key, config_parms_only=False):
172184
use_expanded))
173185

174186

187+
def filter_value(workflow, parm_key, value, by):
188+
""" Filter value by condtional
189+
190+
Currently only used for filter mobile device by device type.
191+
"""
192+
if workflow == "integration_tests" and (parm_key == "android_device" or parm_key == "ios_device"):
193+
filtered_value = filter(lambda device: device.split(":")[0] in by, value)
194+
value = list(filtered_value)
195+
196+
return value
197+
198+
175199
def print_value(value):
176200
""" Print Json formatted string that can be consumed in Github workflow."""
177201
# Eg: for lists,
@@ -276,6 +300,7 @@ def main():
276300
return
277301

278302
value = get_value(args.workflow, args.expanded, args.parm_key, args.config)
303+
value = filter_value(args.workflow, args.parm_key, value, by = args.device)
279304
if args.auto_diff:
280305
value = filter_values_on_diff(args.parm_key, value, args.auto_diff)
281306
print_value(value)
@@ -289,6 +314,7 @@ def parse_cmdline_args():
289314
parser.add_argument('-k', '--parm_key', required=True, help='Print the value of specified key from matrix or config maps.')
290315
parser.add_argument('-a', '--auto_diff', metavar='BRANCH', help='Compare with specified base branch to automatically set matrix options')
291316
parser.add_argument('-o', '--override', help='Override existing value with provided value')
317+
parser.add_argument('-d', '--device', default=['real', 'virtual'], help='Test on which type of mobile devices')
292318
args = parser.parse_args()
293319
return args
294320

scripts/gha/restore_secrets.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
flags.DEFINE_string("repo_dir", os.getcwd(), "Path to C++ SDK Github repo.")
5050
flags.DEFINE_string("passphrase", None, "The passphrase itself.")
5151
flags.DEFINE_string("passphrase_file", None, "Path to file with passphrase.")
52+
flags.DEFINE_string("artifact", None, "Artifact Path, google-services.json will be placed here.")
5253

5354

5455
def main(argv):
@@ -78,6 +79,13 @@ def main(argv):
7879
api = os.path.basename(os.path.dirname(path))
7980
file_name = os.path.basename(path).replace(".gpg", "")
8081
dest_paths = [os.path.join(repo_dir, api, "integration_test", file_name)]
82+
if FLAGS.artifact:
83+
# /<repo_dir>/<artifact>/auth/google-services.json
84+
if "google-services" in path and os.path.isdir(os.path.join(repo_dir, FLAGS.artifact, api)):
85+
dest_paths = [os.path.join(repo_dir, FLAGS.artifact, api, file_name)]
86+
else:
87+
continue
88+
8189
# Some apis like Firestore also have internal integration tests.
8290
if os.path.exists( os.path.join(repo_dir, api, "integration_test_internal")):
8391
dest_paths.append(os.path.join(repo_dir, api,
@@ -93,6 +101,9 @@ def main(argv):
93101
_patch_reverse_id(dest_path)
94102
_patch_bundle_id(dest_path)
95103

104+
if FLAGS.artifact:
105+
return
106+
96107
print("Attempting to patch Dynamic Links uri prefix.")
97108
uri_path = os.path.join(secrets_dir, "dynamic_links", "uri_prefix.txt.gpg")
98109
uri_prefix = _decrypt(uri_path, passphrase)

0 commit comments

Comments
 (0)