Skip to content

Commit 0a6f42e

Browse files
Merge pull request #156 from firebase/feature/ak-integration-tests
Add Windows integration tests to workflow
2 parents ba07b51 + d1ff242 commit 0a6f42e

File tree

3 files changed

+79
-19
lines changed

3 files changed

+79
-19
lines changed

.github/workflows/integration_tests.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ jobs:
7171
- name: Set env vars(all)
7272
run: echo "::set-env name=VCPKG_RESPONSE_FILE::external/vcpkg_${{ env.VCPKG_TRIPLET }}_response_file.txt"
7373

74+
- name: Add msbuild to PATH (windows)
75+
if: startsWith(matrix.os, 'windows')
76+
uses: microsoft/setup-msbuild@v1.0.1
77+
7478
- name: Cache vcpkg C++ dependencies
7579
if: matrix.target_platform == 'Desktop'
7680
id: cache_vcpkg
@@ -91,6 +95,7 @@ jobs:
9195
9296
- name: Install SDK Android prerequisites
9397
if: matrix.target_platform == 'Android'
98+
shell: bash
9499
run: |
95100
build_scripts/android/install_prereqs.sh
96101
@@ -105,6 +110,12 @@ jobs:
105110
NDK_ROOT: '/tmp/android-ndk-r16b'
106111
run: |
107112
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
113+
114+
# Workaround for https://github.com/GoogleCloudPlatform/github-actions/issues/100
115+
# Must be run after the Python setup action
116+
- name: Set CLOUDSDK_PYTHON (Windows)
117+
if: startsWith(matrix.os, 'windows') && !cancelled()
118+
run: echo "::set-env name=CLOUDSDK_PYTHON::${{env.pythonLocation}}\python.exe"
108119
- name: Install Cloud SDK for mobile integration tests
109120
if: matrix.target_platform != 'Desktop' && !cancelled()
110121
uses: GoogleCloudPlatform/github-actions/setup-gcloud@master

scripts/gha/build_testapps.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ def main(argv):
189189
platforms = FLAGS.platforms
190190
testapps = FLAGS.testapps
191191

192+
sdk_dir = _fix_path(FLAGS.sdk_dir)
193+
output_dir = _fix_path(FLAGS.output_directory)
194+
root_dir = _fix_path(FLAGS.root_dir)
195+
provisions_dir = _fix_path(FLAGS.provisions_dir)
196+
192197
update_pod_repo = FLAGS.update_pod_repo
193198
if FLAGS.add_timestamp:
194199
timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S")
@@ -204,7 +209,7 @@ def main(argv):
204209
config = config_reader.read_config()
205210
cmake_flags = _get_desktop_compiler_flags(FLAGS.compiler, config.compilers)
206211
if FLAGS.use_vcpkg:
207-
vcpkg = Vcpkg.generate(os.path.join(FLAGS.sdk_dir, config.vcpkg_dir))
212+
vcpkg = Vcpkg.generate(os.path.join(sdk_dir, config.vcpkg_dir))
208213
vcpkg.install_and_run()
209214
cmake_flags.extend(vcpkg.cmake_flags)
210215

@@ -215,12 +220,12 @@ def main(argv):
215220
testapp=testapp,
216221
platforms=platforms,
217222
api_config=config.get_api(testapp),
218-
output_dir=os.path.expanduser(FLAGS.output_directory),
219-
sdk_dir=os.path.expanduser(FLAGS.sdk_dir),
223+
output_dir=output_dir,
224+
sdk_dir=sdk_dir,
220225
timestamp=timestamp,
221226
builder_dir=pathlib.Path(__file__).parent.absolute(),
222-
root_dir=os.path.expanduser(FLAGS.root_dir),
223-
provisions_dir=os.path.expanduser(FLAGS.provisions_dir),
227+
root_dir=root_dir,
228+
provisions_dir=provisions_dir,
224229
ios_sdk=FLAGS.ios_sdk,
225230
dev_team=config.apple_team_id,
226231
cmake_flags=cmake_flags,
@@ -339,17 +344,21 @@ def _get_desktop_compiler_flags(compiler, compiler_table):
339344

340345
def _build_android(project_dir, sdk_dir):
341346
"""Builds an Android binary (apk)."""
347+
if platform.system() == "Windows":
348+
gradlew = "gradlew.bat"
349+
sdk_dir = sdk_dir.replace("\\", "/") # Gradle misinterprets backslashes.
350+
else:
351+
gradlew = "./gradlew"
342352
logging.info("Patching gradle properties with path to SDK")
343353
gradle_properties = os.path.join(project_dir, "gradle.properties")
344354
with open(gradle_properties, "a+") as f:
345355
f.write("systemProp.firebase_cpp_sdk.dir=" + sdk_dir + "\n")
346356
# This will log the versions of dependencies for debugging purposes.
347-
_run(
348-
["./gradlew", "dependencies", "--configuration", "debugCompileClasspath"])
357+
_run([gradlew, "dependencies", "--configuration", "debugCompileClasspath"])
349358
# Building for Android has a known issue that can be worked around by
350359
# simply building again. Since building from source takes a while, we don't
351360
# want to retry the build if a different error occurred.
352-
build_args = ["./gradlew", "assembleDebug", "--stacktrace"]
361+
build_args = [gradlew, "assembleDebug", "--stacktrace"]
353362
result = _run(args=build_args, capture_output=True, text=True, check=False)
354363
if result.returncode:
355364
if "Execution failed for task ':generateJsonModel" in result.stderr:
@@ -485,11 +494,19 @@ def _run(args, timeout=2400, capture_output=False, text=None, check=True):
485494

486495
def _rm_dir_safe(directory_path):
487496
"""Removes directory at given path. No error if dir doesn't exist."""
497+
logging.info("Deleting %s...", directory_path)
488498
try:
489499
shutil.rmtree(directory_path)
490-
logging.info("Deleted %s", directory_path)
491-
except FileNotFoundError:
492-
logging.warning("Tried to delete %s, but it doesn't exist.", directory_path)
500+
except OSError as e:
501+
# There are two known cases where this can happen:
502+
# The directory doesn't exist (FileNotFoundError)
503+
# A file in the directory is open in another process (PermissionError)
504+
logging.warning("Failed to remove directory:\n%s", e)
505+
506+
507+
def _fix_path(path):
508+
"""Expands ~, normalizes slashes, and converts relative paths to absolute."""
509+
return os.path.abspath(os.path.expanduser(path))
493510

494511

495512
@attr.s(frozen=True, eq=False)

scripts/gha/test_lab.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import os
4343
import random
4444
import string
45+
import shutil
4546
import subprocess
4647
import threading
4748

@@ -86,13 +87,26 @@
8687
" to find available values. If none, will use FTL's default.")
8788

8889

90+
# Full paths to the gCloud SDK tools. On Windows, subprocess.run does not check
91+
# the PATH, so we need to find and supply the full paths.
92+
# shutil.which returns None if it doesn't find a tool.
93+
_GCLOUD = shutil.which("gcloud")
94+
_GSUTIL = shutil.which("gsutil")
95+
96+
8997
def main(argv):
9098
if len(argv) > 1:
9199
raise app.UsageError("Too many command-line arguments.")
92100

93-
testapp_dir = FLAGS.testapp_dir
101+
_verify_gcloud_sdk_command_line_tools()
102+
103+
testapp_dir = _fix_path(FLAGS.testapp_dir)
104+
key_file_path = _fix_path(FLAGS.key_file)
94105
code_platform = FLAGS.code_platform
95106

107+
if not os.path.exists(key_file_path):
108+
raise ValueError("Key file path does not exist: %s" % key_file_path)
109+
96110
android_device = Device(model=FLAGS.android_model, version=FLAGS.android_api)
97111
ios_device = Device(model=FLAGS.ios_model, version=FLAGS.ios_version)
98112

@@ -111,7 +125,7 @@ def main(argv):
111125

112126
logging.info("Testapps found: %s", "\n".join(path for _, _, path in testapps))
113127

114-
_authorize_gcs(FLAGS.key_file)
128+
_authorize_gcs(key_file_path)
115129

116130
gcs_base_dir = _get_base_results_dir()
117131
logging.info("Storing results in %s", _relative_path_to_gs_uri(gcs_base_dir))
@@ -148,6 +162,19 @@ def main(argv):
148162
return 0 if all_success else 1
149163

150164

165+
def _verify_gcloud_sdk_command_line_tools():
166+
"""Verifies the presence of the gCloud SDK's command line tools."""
167+
logging.info("Looking for gcloud and gsutil tools...")
168+
if not _GCLOUD:
169+
logging.error("gcloud not on path")
170+
if not _GSUTIL:
171+
logging.error("gsutil not on path")
172+
if not _GCLOUD or not _GSUTIL:
173+
raise RuntimeError("Could not find required gCloud SDK tool(s)")
174+
subprocess.run([_GCLOUD, "version"], check=True)
175+
subprocess.run([_GSUTIL, "version"], check=True)
176+
177+
151178
def _get_base_results_dir():
152179
"""Defines the object used on GCS for all tests in this run."""
153180
# We generate a unique directory to store the results by appending 4
@@ -162,12 +189,12 @@ def _authorize_gcs(key_file):
162189
"""Activates the service account on GCS and specifies the project."""
163190
subprocess.run(
164191
args=[
165-
"gcloud", "auth", "activate-service-account", "--key-file", key_file
192+
_GCLOUD, "auth", "activate-service-account", "--key-file", key_file
166193
],
167194
check=True)
168195
# Keep using this project for subsequent gcloud commands.
169196
subprocess.run(
170-
args=["gcloud", "config", "set", "project", _PROJECT_ID],
197+
args=[_GCLOUD, "config", "set", "project", _PROJECT_ID],
171198
check=True)
172199

173200

@@ -250,20 +277,25 @@ def _relative_path_to_gs_uri(path):
250277

251278
def _gcs_list_dir(gcs_path):
252279
"""Recursively returns a list of contents for a directory on GCS."""
253-
args = ["gsutil", "ls", "-r", gcs_path]
280+
args = [_GSUTIL, "ls", "-r", gcs_path]
254281
logging.info("Listing GCS contents: %s", " ".join(args))
255282
result = subprocess.run(args=args, capture_output=True, text=True, check=True)
256283
return result.stdout.splitlines()
257284

258285

259286
def _gcs_read_file(gcs_path):
260287
"""Extracts the contents of a file on GCS."""
261-
args = ["gsutil", "cat", gcs_path]
288+
args = [_GSUTIL, "cat", gcs_path]
262289
logging.info("Reading GCS file: %s", " ".join(args))
263290
result = subprocess.run(args=args, capture_output=True, text=True, check=True)
264291
return result.stdout
265292

266293

294+
def _fix_path(path):
295+
"""Expands ~, normalizes slashes, and converts relative paths to absolute."""
296+
return os.path.abspath(os.path.expanduser(path))
297+
298+
267299
@attr.s(frozen=False, eq=False)
268300
class Test(object):
269301
"""Holds data related to the testing of one testapp."""
@@ -293,9 +325,9 @@ def run(self):
293325
def _gcloud_command(self):
294326
"""Returns the args to send this testapp to FTL on the command line."""
295327
if self.platform == _ANDROID:
296-
cmd = ["gcloud", "firebase", "test", "android", "run"]
328+
cmd = [_GCLOUD, "firebase", "test", "android", "run"]
297329
elif self.platform == _IOS:
298-
cmd = ["gcloud", "beta", "firebase", "test", "ios", "run"]
330+
cmd = [_GCLOUD, "beta", "firebase", "test", "ios", "run"]
299331
else:
300332
raise ValueError("Invalid platform, must be 'Android' or 'iOS'")
301333
return cmd + self.device.get_gcloud_flags() + [

0 commit comments

Comments
 (0)