| import json | 
 | import os | 
 | import io | 
 | import unittest | 
 | import zipfile | 
 | from tools.adt.idea.studio import stamper | 
 |  | 
 |  | 
 | def read_file(path, mode = "r"): | 
 |   with open(path, mode) as f: | 
 |     return f.read() | 
 |  | 
 |  | 
 | def get_path(name): | 
 |   return os.path.join(os.getenv("TEST_TMPDIR"), name) | 
 |  | 
 |  | 
 | def create_file(name, content): | 
 |   path = get_path(name) | 
 |   with open(path, "w") as f: | 
 |     f.write(content) | 
 |   return path | 
 |  | 
 |  | 
 | def create_zip(name, contents): | 
 |   path = get_path(name) | 
 |   tmp = 0 | 
 |   with zipfile.ZipFile(path, "w") as zip: | 
 |     for e, v in contents.items(): | 
 |       if isinstance(v, str): | 
 |         zip.writestr(e, v) | 
 |       else: | 
 |         tmp_path = create_zip("%s.%d.zip" % (name, tmp), v) | 
 |         zip.write(tmp_path, e) | 
 |         os.remove(tmp_path) | 
 |   return path | 
 |  | 
 |  | 
 | def read_zip_data(data): | 
 |   res = {} | 
 |   with zipfile.ZipFile(io.BytesIO(data)) as zip: | 
 |     for name in zip.namelist(): | 
 |       data = zip.read(name) | 
 |       if name.endswith(".jar") or name.endswith(".zip"): | 
 |         res[name] = read_zip_data(data) | 
 |       else: | 
 |         res[name] = data.decode("utf-8") | 
 |   return res | 
 |  | 
 |  | 
 | def read_zip(path): | 
 |   return read_zip_data(read_file(path, "rb")) | 
 |  | 
 |  | 
 | class ToolsTest(unittest.TestCase): | 
 |   """Tests tools used to bundle Android Studio.""" | 
 |  | 
 |   def test_overwrite_plugin_version(self): | 
 |     build_txt = create_file("build.txt", "AI-1234.3333") | 
 |     before = create_zip("before.jar", { | 
 |           "ANY-DIR/anyfile.xml": "<version>1</version>" | 
 |     }) | 
 |     after = get_path("res.jar") | 
 |     stamper.main([ | 
 |         "--entry", "ANY-DIR/anyfile.xml", | 
 |         "--build_txt", build_txt, | 
 |         "--stamp", before, after, | 
 |         "--overwrite_plugin_version", | 
 |     ]) | 
 |  | 
 |     self.assertEqual({ | 
 |         "ANY-DIR/anyfile.xml": "<version>1234.3333</version>" | 
 |       }, read_zip(after)) | 
 |  | 
 |   def test_change_since_until(self): | 
 |     build_txt = create_file("build.txt", "AI-1234.3333") | 
 |     before = create_zip("before.jar", { | 
 |           "ANY-DIR/anyfile.xml": "<idea-version since-build=\"1.1\" until-build=\"2.1\">" | 
 |     }) | 
 |     after = get_path("res.jar") | 
 |     stamper.main([ | 
 |         "--entry", "ANY-DIR/anyfile.xml", | 
 |         "--build_txt", build_txt, | 
 |         "--stamp", before, after, | 
 |         "--overwrite_plugin_version", | 
 |     ]) | 
 |  | 
 |     self.assertEqual({ | 
 |         "ANY-DIR/anyfile.xml": "<idea-version since-build=\"1234.3333\" until-build=\"1234.3333\">" | 
 |       }, read_zip(after)) | 
 |  | 
 |   def test_change_since_until_major(self): | 
 |     build_txt = create_file("build.txt", "AI-1234.3333") | 
 |     before = create_zip("before.jar", { | 
 |           "ANY-DIR/anyfile.xml": "<idea-version since-build=\"1.1\" until-build=\"2.1\"/>" | 
 |     }) | 
 |     after = get_path("res.jar") | 
 |     stamper.main([ | 
 |         "--entry", "ANY-DIR/anyfile.xml", | 
 |         "--build_txt", build_txt, | 
 |         "--stamp", before, after, | 
 |         "--overwrite_since_until_builds", | 
 |     ]) | 
 |  | 
 |     self.assertEqual({ | 
 |         "ANY-DIR/anyfile.xml": "<idea-version since-build=\"1234\" until-build=\"1234.*\"/>" | 
 |       }, read_zip(after)) | 
 |  | 
 |   def test_change_since_until_major_new(self): | 
 |     build_txt = create_file("build.txt", "AI-1234.3333") | 
 |     before = create_zip("before.jar", { | 
 |           "ANY-DIR/anyfile.xml": "<id>my.id</id>" | 
 |     }) | 
 |     after = get_path("res.jar") | 
 |     stamper.main([ | 
 |         "--entry", "ANY-DIR/anyfile.xml", | 
 |         "--build_txt", build_txt, | 
 |         "--stamp", before, after, | 
 |         "--overwrite_since_until_builds", | 
 |     ]) | 
 |  | 
 |     self.assertEqual({ | 
 |         "ANY-DIR/anyfile.xml": "<id>my.id</id>\n  <idea-version since-build=\"1234\" until-build=\"1234.*\"/>" | 
 |       }, read_zip(after)) | 
 |  | 
 |   def test_change_since(self): | 
 |     build_txt = create_file("build.txt", "AI-1234.3333") | 
 |     before = create_zip("before.jar", { | 
 |           "ANY-DIR/anyfile.xml": "<idea-version since-build=\"1.1\">" | 
 |     }) | 
 |     after = get_path("res.jar") | 
 |     stamper.main([ | 
 |         "--entry", "ANY-DIR/anyfile.xml", | 
 |         "--build_txt", build_txt, | 
 |         "--stamp", before, after, | 
 |         "--overwrite_plugin_version", | 
 |     ]) | 
 |  | 
 |     self.assertEqual({ | 
 |         "ANY-DIR/anyfile.xml": "<idea-version since-build=\"1234.3333\">" | 
 |       }, read_zip(after)) | 
 |  | 
 |   def test_stamp_product_info(self): | 
 |     build_txt = create_file("build.txt", "AI-1234.3333") | 
 |     before = create_file("product-info.json", json.dumps({ | 
 |       "name": "Studio", | 
 |       "version": "dev build", | 
 |       "buildNumber": "__BUILD_NUMBER__", | 
 |       "bundledPlugins": [ | 
 |         "some.platform.plugin", | 
 |       ], | 
 |       "layout": [ | 
 |         { | 
 |           "name": "some.platform.plugin", | 
 |           "kind": "plugin", | 
 |           "classPath": [], | 
 |         }, | 
 |       ], | 
 |     })) | 
 |     after = get_path("res.json") | 
 |     stamper.main([ | 
 |         "--build_txt", build_txt, | 
 |         "--stamp", before, after, | 
 |         "--stamp_product_info", | 
 |         "--added_plugin", "org.jetbrains.android", "plugins/android/lib/android.jar", "plugins/android/lib/asm.jar", | 
 |     ]) | 
 |     expected = json.dumps({ | 
 |       "name": "Studio", | 
 |       "version": "AI-1234.3333", | 
 |       "buildNumber": "1234.3333", | 
 |       "bundledPlugins": [ | 
 |         "some.platform.plugin", | 
 |         "org.jetbrains.android", | 
 |       ], | 
 |       "layout": [ | 
 |         { | 
 |           "name": "some.platform.plugin", | 
 |           "kind": "plugin", | 
 |           "classPath": [], | 
 |         }, | 
 |         { | 
 |           "name": "org.jetbrains.android", | 
 |           "kind": "plugin", | 
 |           "classPath": [ | 
 |             "plugins/android/lib/android.jar", | 
 |             "plugins/android/lib/asm.jar", | 
 |           ], | 
 |         }, | 
 |       ], | 
 |     }, indent=2) | 
 |     self.assertEqual(expected, read_file(after)) | 
 |  | 
 |   def test_add_essential_plugins(self): | 
 |     volatile = create_file( | 
 |         "volatile.txt", | 
 |         """BUILD_HOSTNAME hostname.c.googlers.com | 
 | BUILD_TIMESTAMP 1746478494 | 
 | BUILD_USERNAME username | 
 | FORMATTED_DATE 2025 May 05 20 54 54 Mon""", | 
 |     ) | 
 |  | 
 |     build_txt = create_file("build.txt", "AI-251.23774.435.2511.SNAPSHOT") | 
 |  | 
 |     before = create_zip( | 
 |         "resources.jar", | 
 |         {"idea/AndroidStudioApplicationInfo.xml": """<!-- | 
 |   ~ Copyright 2000-2013 JetBrains s.r.o. | 
 |   --> | 
 | <component xmlns="http://jetbrains.org/intellij/schema/application-info" | 
 |            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 
 |            xsi:schemaLocation="http://jetbrains.org/intellij/schema/application-info http://jetbrains.org/intellij/schema/ApplicationInfo.xsd"> | 
 |   <version major="2025" minor="1" micro="1" patch="9" full="Narwhal | {0}.{1}.{2} Canary 9" suffix="" eap="true"/> | 
 |   <company name="Google" url="http://developer.android.com"/> | 
 |   <build number="AI-251.23774.435.2511.13434847" date="202505011510" apiVersion="251.23774.435"/> | 
 |   <logo url="/artwork/studio_splash.png"/> | 
 |   <icon svg="/artwork/androidstudio.svg" svg-small="/artwork/androidstudio-small.svg" ico="artwork/androidstudio.ico"/> | 
 |   <icon-eap svg="/artwork/preview/androidstudio.svg" svg-small="/artwork/preview/androidstudio-small.svg"/> | 
 |   <names product="Studio" fullname="Android Studio" script="studio"/> <!-- fullname is used by NPW to show default folder for projects as --> | 
 |   <essential-plugin>com.intellij.java</essential-plugin> | 
 | </component>"""}, | 
 |     ) | 
 |  | 
 |     after = get_path("res.txt") | 
 |  | 
 |     stamper.main([ | 
 |         "--entry", | 
 |         "idea/AndroidStudioApplicationInfo.xml", | 
 |         "--version_file", | 
 |         volatile, | 
 |         "--version_full", | 
 |         "Narwhal | {0}.{1}.{2} Canary 9", | 
 |         "--version_micro", | 
 |         "1", | 
 |         "--version_patch", | 
 |         "9", | 
 |         "--build_txt", | 
 |         build_txt, | 
 |         "--essential_plugins", | 
 |         "com.google.idea.g3plugins", | 
 |         "--stamp_app_info", | 
 |         "--stamp", | 
 |         before, | 
 |         after, | 
 |     ]) | 
 |  | 
 |     self.maxDiff = None | 
 |  | 
 |     self.assertEqual( | 
 |         {"idea/AndroidStudioApplicationInfo.xml": """<!-- | 
 |   ~ Copyright 2000-2013 JetBrains s.r.o. | 
 |   --> | 
 | <component xmlns="http://jetbrains.org/intellij/schema/application-info" | 
 |            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 
 |            xsi:schemaLocation="http://jetbrains.org/intellij/schema/application-info http://jetbrains.org/intellij/schema/ApplicationInfo.xsd"> | 
 |   <version major="2025" minor="1" micro="1" patch="9" full="Narwhal | {0}.{1}.{2} Canary 9" suffix="" eap="true"/> | 
 |   <company name="Google" url="http://developer.android.com"/> | 
 |   <build number="AI-251.23774.435.2511.13434847" date="202505011510" apiVersion="251.23774.435"/> | 
 |   <logo url="/artwork/studio_splash.png"/> | 
 |   <icon svg="/artwork/androidstudio.svg" svg-small="/artwork/androidstudio-small.svg" ico="artwork/androidstudio.ico"/> | 
 |   <icon-eap svg="/artwork/preview/androidstudio.svg" svg-small="/artwork/preview/androidstudio-small.svg"/> | 
 |   <names product="Studio" fullname="Android Studio" script="studio"/> <!-- fullname is used by NPW to show default folder for projects as --> | 
 |   <essential-plugin>com.intellij.java</essential-plugin> | 
 |   <essential-plugin>com.google.idea.g3plugins</essential-plugin> | 
 | </component>"""}, | 
 |         read_zip(after), | 
 |     ) | 
 |  | 
 |   def test_add_essential_plugins_to_empty_list(self): | 
 |     volatile = create_file( | 
 |         "volatile.txt", | 
 |         """BUILD_HOSTNAME hostname.c.googlers.com | 
 | BUILD_TIMESTAMP 1746478494 | 
 | BUILD_USERNAME username | 
 | FORMATTED_DATE 2025 May 05 20 54 54 Mon""", | 
 |     ) | 
 |  | 
 |     build_txt = create_file("build.txt", "AI-251.23774.435.2511.SNAPSHOT") | 
 |  | 
 |     before = create_zip( | 
 |         "resources.jar", | 
 |         {"idea/AndroidStudioApplicationInfo.xml": """<!-- | 
 |   ~ Copyright 2000-2013 JetBrains s.r.o. | 
 |   --> | 
 | <component xmlns="http://jetbrains.org/intellij/schema/application-info" | 
 |            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 
 |            xsi:schemaLocation="http://jetbrains.org/intellij/schema/application-info http://jetbrains.org/intellij/schema/ApplicationInfo.xsd"> | 
 |   <version major="2025" minor="1" micro="1" patch="9" full="Narwhal | {0}.{1}.{2} Canary 9" suffix="" eap="true"/> | 
 |   <company name="Google" url="http://developer.android.com"/> | 
 |   <build number="AI-251.23774.435.2511.13434847" date="202505011510" apiVersion="251.23774.435"/> | 
 |   <logo url="/artwork/studio_splash.png"/> | 
 |   <icon svg="/artwork/androidstudio.svg" svg-small="/artwork/androidstudio-small.svg" ico="artwork/androidstudio.ico"/> | 
 |   <icon-eap svg="/artwork/preview/androidstudio.svg" svg-small="/artwork/preview/androidstudio-small.svg"/> | 
 |   <names product="Studio" fullname="Android Studio" script="studio"/> <!-- fullname is used by NPW to show default folder for projects as --> | 
 | </component>"""}, | 
 |     ) | 
 |  | 
 |     after = get_path("res.txt") | 
 |  | 
 |     stamper.main([ | 
 |         "--entry", | 
 |         "idea/AndroidStudioApplicationInfo.xml", | 
 |         "--version_file", | 
 |         volatile, | 
 |         "--version_full", | 
 |         "Narwhal | {0}.{1}.{2} Canary 9", | 
 |         "--version_micro", | 
 |         "1", | 
 |         "--version_patch", | 
 |         "9", | 
 |         "--build_txt", | 
 |         build_txt, | 
 |         "--essential_plugins", | 
 |         "com.google.idea.g3plugins", | 
 |         "--stamp_app_info", | 
 |         "--stamp", | 
 |         before, | 
 |         after, | 
 |     ]) | 
 |  | 
 |     self.maxDiff = None | 
 |  | 
 |     self.assertEqual( | 
 |         {"idea/AndroidStudioApplicationInfo.xml": """<!-- | 
 |   ~ Copyright 2000-2013 JetBrains s.r.o. | 
 |   --> | 
 | <component xmlns="http://jetbrains.org/intellij/schema/application-info" | 
 |            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 
 |            xsi:schemaLocation="http://jetbrains.org/intellij/schema/application-info http://jetbrains.org/intellij/schema/ApplicationInfo.xsd"> | 
 |   <version major="2025" minor="1" micro="1" patch="9" full="Narwhal | {0}.{1}.{2} Canary 9" suffix="" eap="true"/> | 
 |   <company name="Google" url="http://developer.android.com"/> | 
 |   <build number="AI-251.23774.435.2511.13434847" date="202505011510" apiVersion="251.23774.435"/> | 
 |   <logo url="/artwork/studio_splash.png"/> | 
 |   <icon svg="/artwork/androidstudio.svg" svg-small="/artwork/androidstudio-small.svg" ico="artwork/androidstudio.ico"/> | 
 |   <icon-eap svg="/artwork/preview/androidstudio.svg" svg-small="/artwork/preview/androidstudio-small.svg"/> | 
 |   <names product="Studio" fullname="Android Studio" script="studio"/> <!-- fullname is used by NPW to show default folder for projects as --> | 
 |   <essential-plugin>com.google.idea.g3plugins</essential-plugin> | 
 | </component>"""}, | 
 |         read_zip(after), | 
 |     ) | 
 |  | 
 |   def test_replace_build_number(self): | 
 |     stable = create_file("info.txt", "BUILD_EMBED_LABEL 3333") | 
 |     before = create_file("like_build.txt", "AI-__BUILD_NUMBER__") | 
 |     after = get_path("res.txt") | 
 |     stamper.main([ | 
 |         "--info_file", stable, | 
 |         "--version_component", "1234", | 
 |         "--stamp", before, after, | 
 |         "--replace_build_number" | 
 |     ]) | 
 |     self.assertEqual("AI-1234.3333", read_file(after)) | 
 |  | 
 |   def test_stamp_app_info(self): | 
 |     volatile = create_file("volatile.txt", "BUILD_TIMESTAMP 1597877532") | 
 |     build_txt = create_file("build.txt", "AI-1234.3333") | 
 |     before = create_zip("resources.jar", { | 
 |         "idea/AndroidStudioApplicationInfo.xml": """ | 
 |       <build number="AI-__BUILD__" date="__BUILD_DATE__"> | 
 |       <version major="4" minor="3" micro="2" patch="1" full="a" eap="false" >""" | 
 |     }) | 
 |     after = get_path("res.zip") | 
 |     stamper.main([ | 
 |         "--entry", "idea/AndroidStudioApplicationInfo.xml", | 
 |         "--version_file", volatile, | 
 |         "--build_txt", build_txt, | 
 |         "--version_micro", "33", | 
 |         "--version_patch", "44", | 
 |         "--version_full", "{0} Canary 5", | 
 |         "--stamp", before, after, | 
 |         "--stamp_app_info" | 
 |     ]) | 
 |     self.maxDiff=None | 
 |     self.assertEqual({ | 
 |         "idea/AndroidStudioApplicationInfo.xml": """ | 
 |       <build number="AI-1234.3333" date="202008192252"> | 
 |       <version major="4" minor="3" micro="33" patch="44" full="{0} Canary 5" eap="false" >""" | 
 |     }, read_zip(after)) | 
 |  | 
 |   def test_stamp_day_in_full(self): | 
 |     volatile = create_file("volatile.txt", "BUILD_TIMESTAMP 1597877532") | 
 |     build_txt = create_file("build.txt", "AI-1234.3333") | 
 |     before = create_zip("resources.jar", { | 
 |         "idea/AndroidStudioApplicationInfo.xml": """ | 
 |       <build number="AI-__BUILD__" date="__BUILD_DATE__"> | 
 |       <version major="4" minor="3" micro="2" patch="1" full="a" eap="false" >""" | 
 |     }) | 
 |     after = get_path("res.zip") | 
 |     stamper.main([ | 
 |         "--entry", "idea/AndroidStudioApplicationInfo.xml", | 
 |         "--version_file", volatile, | 
 |         "--build_txt", build_txt, | 
 |         "--version_micro", "33", | 
 |         "--version_patch", "44", | 
 |         "--version_full", "{0} Nightly __BUILD_DAY__", | 
 |         "--replace_build_day", | 
 |         "--stamp", before, after, | 
 |         "--stamp_app_info" | 
 |     ]) | 
 |     self.maxDiff=None | 
 |     self.assertEqual({ | 
 |         "idea/AndroidStudioApplicationInfo.xml": """ | 
 |       <build number="AI-1234.3333" date="202008192252"> | 
 |       <version major="4" minor="3" micro="33" patch="44" full="{0} Nightly 2020-08-19" eap="false" >""" | 
 |     }, read_zip(after)) | 
 |  | 
 |   def test_replace_subs(self): | 
 |     volatile = create_file("volatile.txt", "BUILD_TIMESTAMP 1597877532") | 
 |     before = create_file("file.txt", "Some {a} text {b} here") | 
 |     after = get_path("after.txt") | 
 |     stamper.main([ | 
 |         "--version_file", volatile, | 
 |         "--stamp", before, after, | 
 |         "--substitute", "{a}", "AA", | 
 |         "--substitute", "{b}", "b__BUILD_DAY__b", | 
 |         "--replace_build_day" | 
 |     ]) | 
 |     self.assertEqual("Some AA text b2020-08-19b here", read_file(after)) | 
 |  | 
 |   def test_inject(self): | 
 |     build_txt = create_file("build.txt", "AI-1234.3333") | 
 |     before = create_zip("before.jar", { | 
 |           "ANY-DIR/anyfile.xml": "<id>x</id>" | 
 |     }) | 
 |     after = get_path("res.jar") | 
 |     stamper.main([ | 
 |         "--entry", "ANY-DIR/anyfile.xml", | 
 |         "--build_txt", build_txt, | 
 |         "--stamp", before, after, | 
 |         "--overwrite_plugin_version", | 
 |     ]) | 
 |  | 
 |     self.assertEqual({ | 
 |         "ANY-DIR/anyfile.xml": "<id>x</id>\n" + | 
 |         "  <idea-version since-build=\"1234.3333\" until-build=\"1234.3333\"/>\n" + | 
 |         "  <version>1234.3333</version>" | 
 |       }, read_zip(after)) | 
 |  | 
 |  | 
 | if __name__ == "__main__": | 
 |   unittest.main() |