import unittest from lihont import theme_patch ACCESSOR = """\ .class public final LX/0LAr; .super Ljava/lang/Object; .source "SourceFile" .method public static LIZ(LX/0LYA;)LX/0LFj; .registers 2 sget-object v0, LX/0LAs;->LIZ:LX/0MO7; invoke-interface {p0, v0}, LX/0LYA;->LJJIZ(LX/0MNv;)Ljava/lang/Object; move-result-object v0 check-cast v0, LX/0LFj; return-object v0 .end method .method public static LIZIZ(LX/0LYA;)LX/1MiX; .registers 2 .prologue sget-object v0, LX/1MiY;->LIZ:LX/0MO7; invoke-interface {p0, v0}, LX/0LYA;->LJJIZ(LX/0MNv;)Ljava/lang/Object; move-result-object v0 check-cast v0, LX/1MiX; return-object v0 .end method """ LABEL = """.class public final LX/0Lis; .super Ljava/lang/Object; .method public static final LIZIZ(LX/0DMR;LX/0LYA;I)V .registers 20 invoke-static {v0}, LX/0LAr;->LIZIZ(LX/0LYA;)LX/1MiX; move-result-object v1 iget-wide v14, v1, LX/1MiX;->d4:J return-void .end method .method public static final LIZJ(Ljava/lang/String;ZZZLkotlin/jvm/functions/Function0;LX/0LYA;II)V .registers 40 invoke-static {v0}, LX/0LAr;->LIZIZ(LX/0LYA;)LX/1MiX; move-result-object v11 iget-wide v12, v11, LX/1MiX;->d4:J invoke-static {v0}, LX/0LAr;->LJ(LX/0LYA;)LX/0LAb; .line 83886186 move-result-object v3 .line 83886187 iget-wide v8, v3, LX/1MiX;->d4:J :goto_6d return-void .end method """ ROW = """\ .class public final LX/0LDf; .super Ljava/lang/Object; .method public final invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; .registers 52 invoke-interface {v0}, LX/0LYA;->LJ()V .line 33555008 .line 33555009 .line 33555010 move-object/from16 v9, v17 .line 33555011 .line 33555012 invoke-static {v9, v1, v13}, LX/0M8m;->LIZIZ(LX/0M1d;ZLkotlin/jvm/functions/Function1;)LX/0M1d; .line 33555013 move-result-object v1 invoke-interface {v4, v0}, LX/0LDi;->LJIIJJI(LX/0LYA;)J move-result-wide v4 .line 33555175 invoke-static {v8, v8, v7, v7}, LX/0Kzn;->LIZJ(FFFF)LX/0LQf; .line 33555176 .line 33555177 move-result-object v7 move-object/from16 v6, v17 return-object v1 .end method """ CARD = """.class public final LX/0Lk9; .super Ljava/lang/Object; .method public static final LIZ(LX/0CX2;ZZLcom/ss/android/ugc/aweme/setting/ui/rvmpcompose/group/content/cell/LanguageVM;LX/0LYA;I)V .registers 40 invoke-static {v15}, LX/0Kzn;->LIZIZ(F)LX/0LQf; move-result-object v15 invoke-static {v11, v0, v1, v15}, LX/0L5k;->LIZIZ(LX/0M1d;JLX/0LQx;)LX/0M1d; .line 33555200 .line 33555201 move-result-object v0 sget-object v1, LX/0L0L;->LIZ:LX/0L0J; return-void .end method """ class ColoursAccessorTest(unittest.TestCase): def test_hook_sits_between_the_read_and_the_cast(self): text = theme_patch.rewrite_colors_accessor(ACCESSOR) self.assertIn( " move-result-object v0\n\n" " invoke-static {v0, p0}, %s\n\n" " move-result-object v0\n\n" " check-cast v0, LX/1MiX;\n" % theme_patch.HOOK, text) def test_other_methods_are_untouched(self): text = theme_patch.rewrite_colors_accessor(ACCESSOR) self.assertEqual(text.count("NativeTheme"), 1) self.assertIn("check-cast v0, LX/0LFj;\n\n return-object v0\n", text) def test_a_second_pass_is_refused(self): once = theme_patch.rewrite_colors_accessor(ACCESSOR) with self.assertRaises(ValueError): theme_patch.rewrite_colors_accessor(once) def test_a_missing_method_is_refused(self): with self.assertRaises(ValueError): theme_patch.rewrite_colors_accessor(ACCESSOR.replace("LIZIZ", "LIZZZ")) def test_an_unexpected_register_layout_is_refused(self): with self.assertRaises(ValueError): theme_patch.rewrite_colors_accessor( ACCESSOR.replace(".registers 2\n .prologue", ".registers 3\n .prologue")) def test_runtime_target_matches_the_hook(self): self.assertEqual(theme_patch.runtime_targets(), [theme_patch.HOOK]) class SectionLabelTest(unittest.TestCase): def test_both_reads_in_the_group_title_are_rewritten(self): text = theme_patch.rewrite_section_label(LABEL) self.assertIn( " invoke-static/range {v11 .. v11}, %s\n\n move-result-wide v12\n" % theme_patch.SECTION_HOOK, text) self.assertIn( " invoke-static/range {v3 .. v3}, %s\n\n move-result-wide v8\n" % theme_patch.SECTION_HOOK, text) self.assertEqual(text.count("sectionLabel"), 2) # The other composable keeps reading the token itself. self.assertIn("iget-wide v14, v1, LX/1MiX;->d4:J", text) def test_a_second_pass_is_refused(self): once = theme_patch.rewrite_section_label(LABEL) with self.assertRaises(ValueError): theme_patch.rewrite_section_label(once) def test_a_missing_composable_is_refused(self): with self.assertRaises(ValueError): theme_patch.rewrite_section_label( LABEL.replace(".method public static final LIZJ(", ".method public static final LIZZ(")) def test_a_title_with_a_different_number_of_reads_is_refused(self): with self.assertRaises(ValueError): theme_patch.rewrite_section_label( LABEL.replace("v8, v3, LX/1MiX;->d4:J", "v8, v3, LX/1MiX;->a4:J")) def test_section_target_is_the_hook(self): self.assertEqual(theme_patch.section_targets(), [theme_patch.SECTION_HOOK]) class RowLambdaTest(unittest.TestCase): def test_modifier_and_shape_are_routed_through_the_hooks(self): text = theme_patch.rewrite_row_lambda(ROW) self.assertIn( " move-object/from16 v9, v17\n" " invoke-static {v9, v0}, %s\n\n" " move-result-object v9\n\n" " check-cast v9, LX/0M1d;\n" % theme_patch.ROW_MODIFIER_HOOK, text) self.assertIn("invoke-static {v8, v8, v7, v7, v0}, %s\n" % theme_patch.ROW_SHAPE_HOOK, text) self.assertIn(" move-result-object v7\n\n check-cast v7, LX/0LQf;\n", text) self.assertNotIn("LX/0Kzn;->LIZJ", text) # The original semantics call still follows the padded modifier. self.assertIn("invoke-static {v9, v1, v13}, LX/0M8m;->LIZIZ", text) def test_a_second_pass_is_refused(self): once = theme_patch.rewrite_row_lambda(ROW) with self.assertRaises(ValueError): theme_patch.rewrite_row_lambda(once) def test_a_missing_shape_call_is_refused(self): with self.assertRaises(ValueError): theme_patch.rewrite_row_lambda(ROW.replace("LIZJ(FFFF)", "LIZJ(FF)")) def test_a_missing_modifier_start_is_refused(self): with self.assertRaises(ValueError): theme_patch.rewrite_row_lambda( ROW.replace("move-object/from16 v9, v17", "move-object/from16 v9, v18")) def test_row_targets_are_the_hooks(self): self.assertEqual(theme_patch.row_targets(), [theme_patch.ROW_MODIFIER_HOOK, theme_patch.ROW_SHAPE_HOOK]) class LanguageCardTest(unittest.TestCase): def test_the_card_background_call_goes_through_the_hook(self): text = theme_patch.rewrite_language_card(CARD) self.assertIn(" invoke-static {v11, v0, v1, v15, v3}, %s\n" % theme_patch.CARD_HOOK, text) self.assertIn(" move-result-object v0\n\n check-cast v0, LX/0M1d;\n", text) self.assertNotIn("LX/0L5k;->LIZIZ", text) # The rounded shape built just before it is TikTok's own call and stays. self.assertIn("LX/0Kzn;->LIZIZ(F)LX/0LQf;", text) def test_a_second_pass_is_refused(self): once = theme_patch.rewrite_language_card(CARD) with self.assertRaises(ValueError): theme_patch.rewrite_language_card(once) def test_a_missing_background_call_is_refused(self): with self.assertRaises(ValueError): theme_patch.rewrite_language_card(CARD.replace("v11, v0, v1, v15}", "v10, v0, v1, v15}")) def test_card_target_is_the_hook(self): self.assertEqual(theme_patch.card_targets(), [theme_patch.CARD_HOOK]) if __name__ == "__main__": unittest.main()