blob: 9b1936ac2bfe7e5fe082bf3d4df5705d60f9d2f7 [file] [log] [blame]
import json
import xml.etree.ElementTree as ET
import os
import re
import unittest
import zipfile
PLATFORMS = ["linux", "win", "mac", "mac_arm"]
def _is_symlink(zipinfo):
return (zipinfo.external_attr & 0x20000000) > 0
class StudioTests(unittest.TestCase):
"""Performs basic tests on studio artifacts.
"""
def test_studio_files(self):
actual = {}
for platform in PLATFORMS:
name = "tools/adt/idea/studio/android-studio.%s.zip" % platform
with zipfile.ZipFile(name) as file:
actual[platform] = sorted(file.namelist())
expected = {}
for platform in PLATFORMS:
with open("tools/adt/idea/studio/tests/expected_%s.txt" % platform, "r") as txt:
expected[platform] = [line.strip() for line in sorted(txt.readlines())]
for platform in PLATFORMS:
if expected != actual:
undeclared_dir = os.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
with open("%s/expected_%s.txt" % (undeclared_dir, platform), "w") as new_ex:
new_ex.writelines([line + "\n" for line in actual[platform]])
print("You can find the newly expected file in the undeclared output directory.")
for platform in PLATFORMS:
i = 0
while i < len(actual[platform]) and i < len(expected[platform]):
self.assertEqual(actual[platform][i], expected[platform][i], "Platform %s #%d - Expected \"%s\", got \"%s\"" % (platform, i, expected[platform][i], actual[platform][i]))
i += 1
self.assertEqual(i, len(expected[platform]), "Expected item did not appear")
self.assertEqual(i, len(actual[platform]), "Unexpected item")
def test_version_metadata(self):
# Parse version metadata.
with zipfile.ZipFile(f"tools/adt/idea/studio/android-studio.linux.zip", "r") as distro:
build_txt = distro.read("android-studio/build.txt").decode("utf-8")
with zipfile.ZipFile(distro.open("android-studio/lib/resources.jar")) as jar:
app_xml = ET.parse(jar.open("idea/AndroidStudioApplicationInfo.xml"))
with zipfile.ZipFile(distro.open("android-studio/plugins/android/lib/android.jar")) as jar:
android_plugin_xml = ET.parse(jar.open("META-INF/plugin.xml"))
with zipfile.ZipFile(distro.open("android-studio/plugins/Kotlin/lib/kotlin-plugin.jar")) as jar:
kotlin_plugin_xml = ET.parse(jar.open("META-INF/plugin.xml"))
# The build number should have 5 components and it should match build.txt.
namespace = "http://jetbrains.org/intellij/schema/application-info"
(build,) = app_xml.findall(f"./{{{namespace}}}build")
build_number = build.get("number") or self.fail("build number not found")
self.assertRegex(build_number, r"AI-\d+\.\d+\.\d+\.\d+\.(\d+|SNAPSHOT)")
self.assertEqual(build_number, build_txt, "Build number does not match build.txt")
# The version suffix should be specified, otherwise ApplicationInfoImpl uses a default for EAPs.
(version,) = app_xml.findall(f"./{{{namespace}}}version")
version_suffix = version.get("suffix")
self.assertEqual(version_suffix, "", "Unexpected version suffix")
# The API version (for plugin compatibility) should be a 3-component IntelliJ build number.
api_version = build.get("apiVersion") or self.fail("apiVersion not found")
self.assertRegex(api_version, r"\d+\.\d+\.\d+")
# Bundled Android plugins should have a version and compatibility range matching the IDE.
(android_plugin_version,) = android_plugin_xml.findall("./version")
self.assertEqual(android_plugin_version.text, build_number.removeprefix("AI-"))
(android_plugin_compat,) = android_plugin_xml.findall("idea-version")
self.assertEqual(android_plugin_compat.get("since-build"), api_version)
self.assertEqual(android_plugin_compat.get("until-build"), api_version)
# Bundled platform plugins should have a compatibility range matching the IDE.
(kotlin_plugin_compat,) = kotlin_plugin_xml.findall("idea-version")
self.assertEqual(kotlin_plugin_compat.get("since-build"), api_version)
self.assertTrue(kotlin_plugin_compat.get("until-build").startswith(api_version))
def test_mac_contents_clean(self):
name = "tools/adt/idea/studio/android-studio.mac.zip"
file = zipfile.ZipFile(name)
for f in file.namelist():
m = re.search("Android Studio.*\\.app/Contents/([^/]+)$", f)
if m:
self.assertEqual(m.group(1), "Info.plist", "Only Info.plist should be present in Contents (Found " + m.group(1) + ")")
def test_no_build_files(self):
for platform in PLATFORMS:
name = "tools/adt/idea/studio/android-studio.%s.zip" % platform
with zipfile.ZipFile(name) as file:
for f in file.infolist():
self.assertFalse(f.filename.endswith("/BUILD") or f.filename.endswith("/BUILD.bazel"),
"Unexpected BUILD file in zip " + name + ": " + f.filename)
def test_game_tools_artifacts_are_present(self):
all_required = [
"plugins/android/lib/game-tools.jar",
"plugins/android/lib/game-tools-protos.jar",
]
archive_files_required = {
"linux": [
"bin/game-tools.sh",
"bin/profiler.sh",
],
"win": [
"bin/game-tools.bat",
"bin/profiler.bat",
],
}
for platform in archive_files_required.keys():
name = "tools/adt/idea/studio/android-studio.%s.zip" % platform
with zipfile.ZipFile(name) as file:
files = file.namelist()
for req in all_required + archive_files_required[platform]:
if not any(file_name.endswith(req) for file_name in files):
self.fail("Required file " + req + " not found in " + platform + " distribution.")
def test_profiler_artifacts_are_present(self):
all_required = [
"plugins/android/resources/perfa.jar",
"plugins/android/resources/profilers-transform.jar",
]
for abi in ["x86", "arm64-v8a", "armeabi-v7a"]:
all_required += [
"plugins/android/resources/perfetto/%s/libperfetto.so" % abi,
"plugins/android/resources/perfetto/%s/perfetto" % abi,
"plugins/android/resources/perfetto/%s/traced" % abi,
"plugins/android/resources/perfetto/%s/traced_probes" % abi,
"plugins/android/resources/simpleperf/%s/simpleperf" % abi,
"plugins/android/resources/transport/%s/transport" % abi,
"plugins/android/resources/transport/native/agent/%s/libjvmtiagent.so" % abi,
]
archive_files_required = {
"linux": [
"plugins/android/resources/trace_processor_daemon/trace_processor_daemon"
],
"mac": [
"plugins/android/resources/trace_processor_daemon/trace_processor_daemon"
],
"win": [
"plugins/android/resources/trace_processor_daemon/trace_processor_daemon.exe"
],
}
for platform in archive_files_required.keys():
name = "tools/adt/idea/studio/android-studio.%s.zip" % platform
with zipfile.ZipFile(name) as file:
files = file.namelist()
for req in all_required + archive_files_required[platform]:
if not any(file_name.endswith(req) for file_name in files):
self.fail("Required file " + req + "not found in " + platform + " distribution.")
def test_trace_agent_jar_present(self):
"""Tests that trace_agent.jar is included in distribution"""
expected = "plugins/android/resources/trace_agent.jar"
for platform in PLATFORMS:
name = "tools/adt/idea/studio/android-studio.%s.zip" % platform
with zipfile.ZipFile(name) as file:
files = file.namelist()
if not any(file_name.endswith(expected) for file_name in files):
self.fail("Required file not found in distribution: " + expected)
def test_mac_attributes(self):
name = "tools/adt/idea/studio/android-studio.mac.zip"
with zipfile.ZipFile(name) as file:
found = False
for f in file.infolist():
is_symlink = _is_symlink(f)
if f.filename.endswith("Contents/jbr/Contents/MacOS/libjli.dylib"):
found = True
self.assertFalse(is_symlink, "Contents/jbr/Contents/MacOS/libjli.dylib should not be symlink")
elif f.filename.endswith("Contents/MacOS/studio"):
self.assertFalse(f.external_attr == 0x1ED0000, "studio should be \"-rwxr-xr-x\"")
self.assertFalse(is_symlink, f.filename + " should not be a symlink")
elif f.filename.endswith(".app/Contents/Info.plist"):
self.assertTrue(f.external_attr == 0x81B40000, "Info.plist should be 0x81B40000 \"-rw-rw-r--\" but is (%x)" % f.external_attr)
else:
self.assertFalse(f.external_attr == 0, "Unix attributes are missing from the entry")
self.assertFalse(is_symlink, f.filename + " should not be a symlink")
self.assertTrue(found, "Android Studio.*.app/Contents/jbr/Contents/MacOS/libjli.dylib not found")
def test_all_files_writable(self):
for platform in PLATFORMS:
name = "tools/adt/idea/studio/android-studio.%s.zip" % platform
with zipfile.ZipFile(name) as file:
for f in file.infolist():
if f.external_attr & 0x1800000 != 0x1800000:
self.fail("Found file without full read/write permissions: %s %x" % (f.filename, f.external_attr))
def test_product_info(self):
# Ensure product-info.json contains metadata for our Bazel-built plugins (b/393595344).
with zipfile.ZipFile(f"tools/adt/idea/studio/android-studio.linux.zip", "r") as distro:
product_info = json.loads(distro.read("android-studio/product-info.json").decode("utf-8"))
zip_entries = set(distro.namelist())
self.assertIn("org.jetbrains.android", product_info["bundledPlugins"])
[android_plugin_layout] = [p for p in product_info["layout"] if p["kind"] == "plugin" and p["name"] == "org.jetbrains.android"]
classpath_jars = android_plugin_layout["classPath"]
self.assertNotEqual(0, len(classpath_jars))
for jar in classpath_jars:
self.assertTrue(f"android-studio/{jar}" in zip_entries, f"product-info.json refers to non-existent file: {jar}")
# Ensure our metadata format matches JetBrains metadata (to hopefully catch schema changes).
self.assertIn("com.intellij.java", product_info["bundledPlugins"])
[java_plugin_layout] = [p for p in product_info["layout"] if p["kind"] == "plugin" and p["name"] == "com.intellij.java"]
self.assertEqual(android_plugin_layout.keys(), java_plugin_layout.keys(), "did the schema for product-info.json change?")
java_plugin_jar = java_plugin_layout["classPath"][0]
self.assertRegex(java_plugin_jar, r"^plugins/java/lib/[^/]*\.jar$", "did the scheme for product-info.json change?")
if __name__ == "__main__":
unittest.main()