import textwrap import logger import test_engine log = logger.Logger(__name__, logger.INFO) def _replace_col_name(data, old_name, new_name): """For verifying data, renames a column in the header in-place.""" data[0] = [(new_name if c == old_name else c) for c in data[0]] class TestRenames2(test_engine.EngineTestCase): # Another test for column renames, which tests crazier interconnected formulas. # This one includes a bunch of cases where renames fail, marked as TODOs. def setUp(self): super(TestRenames2, self).setUp() # Create a schema with several tables including some references and lookups. self.apply_user_action(["AddTable", "People", [ {"id": "name", "type": "Text"} ]]) self.apply_user_action(["AddTable", "Games", [ {"id": "name", "type": "Text"}, {"id": "winner", "type": "Ref:People", "isFormula": True, "formula": "Entries.lookupOne(game=$id, rank=1).person"}, {"id": "second", "type": "Ref:People", "isFormula": True, "formula": "Entries.lookupOne(game=$id, rank=2).person"}, ]]) self.apply_user_action(["AddTable", "Entries", [ {"id": "game", "type": "Ref:Games"}, {"id": "person", "type": "Ref:People"}, {"id": "rank", "type": "Int"}, ]]) # Fill it with some sample data. self.add_records("People", ["name"], [ ["Bob"], ["Alice"], ["Carol"], ["Doug"], ["Eve"]]) self.add_records("Games", ["name"], [ ["ChessA"], ["GoA"], ["ChessB"], ["CheckersA"]]) self.add_records("Entries", ["game", "person", "rank"], [ [ 1, 2, 1], [ 1, 4, 2], [ 2, 1, 2], [ 2, 2, 1], [ 3, 4, 1], [ 3, 3, 2], [ 4, 5, 1], [ 4, 1, 2], ]) # Check the data, to see it, and confirm that lookups work. self.assertTableData("People", cols="subset", data=[ [ "id", "name" ], [ 1, "Bob" ], [ 2, "Alice" ], [ 3, "Carol" ], [ 4, "Doug" ], [ 5, "Eve" ], ]) self.assertTableData("Games", cols="subset", data=[ [ "id", "name" , "winner", "second" ], [ 1, "ChessA" , 2, 4, ], [ 2, "GoA" , 2, 1, ], [ 3, "ChessB" , 4, 3, ], [ 4, "CheckersA" , 5, 1 ], ]) # This was just setpu. Now create some crazy formulas that overuse references in crazy ways. self.partner_names = textwrap.dedent( """ games = Entries.lookupRecords(person=$id).game partners = [e.person for g in games for e in Entries.lookupRecords(game=g)] return ' '.join(p.name for p in partners if p.id != $id) """) self.partner = textwrap.dedent( """ game = Entries.lookupOne(person=$id).game next(e.person for e in Entries.lookupRecords(game=game) if e.person != rec) """).strip() self.add_column("People", "N", formula="$name.upper()") self.add_column("People", "Games_Won", formula=( "' '.join(e.game.name for e in Entries.lookupRecords(person=$id, rank=1))")) self.add_column("People", "PartnerNames", formula=self.partner_names) self.add_column("People", "partner", type="Ref:People", formula=self.partner) self.add_column("People", "partner4", type="Ref:People", formula=( "$partner.partner.partner.partner")) # Make it hard to follow references by using the same names in different tables. self.add_column("People", "win", type="Ref:Games", formula="Entries.lookupOne(person=$id, rank=1).game") self.add_column("Games", "win", type="Ref:People", formula="$winner") self.add_column("Games", "win3_person_name", formula="$win.win.win.name") self.add_column("Games", "win4_game_name", formula="$win.win.win.win.name") # This is just for help us know which columns have which rowIds. self.assertTableData("_grist_Tables_column", cols="subset", data=[ [ "id", "parentId", "colId" ], [ 1, 1, "manualSort" ], [ 2, 1, "name" ], [ 3, 2, "manualSort" ], [ 4, 2, "name" ], [ 5, 2, "winner" ], [ 6, 2, "second" ], [ 7, 3, "manualSort" ], [ 8, 3, "game" ], [ 9, 3, "person" ], [ 10, 3, "rank" ], [ 11, 1, "N" ], [ 12, 1, "Games_Won" ], [ 13, 1, "PartnerNames" ], [ 14, 1, "partner" ], [ 15, 1, "partner4" ], [ 16, 1, "win" ], [ 17, 2, "win" ], [ 18, 2, "win3_person_name" ], [ 19, 2, "win4_game_name" ], ]) # Check the data before we start on the renaming. self.people_data = [ [ "id", "name" , "N", "Games_Won", "PartnerNames", "partner", "partner4", "win" ], [ 1, "Bob" , "BOB", "", "Alice Eve" , 2, 4 , 0 ], [ 2, "Alice", "ALICE", "ChessA GoA", "Doug Bob" , 4, 2 , 1 ], [ 3, "Carol", "CAROL", "", "Doug" , 4, 2 , 0 ], [ 4, "Doug" , "DOUG", "ChessB", "Alice Carol" , 2, 4 , 3 ], [ 5, "Eve" , "EVE", "CheckersA", "Bob" , 1, 2 , 4 ], ] self.games_data = [ [ "id", "name" , "winner", "second", "win", "win3_person_name", "win4_game_name" ], [ 1, "ChessA" , 2, 4 , 2 , "Alice" , "ChessA" ], [ 2, "GoA" , 2, 1 , 2 , "Alice" , "ChessA" ], [ 3, "ChessB" , 4, 3 , 4 , "Doug" , "ChessB" ], [ 4, "CheckersA" , 5, 1 , 5 , "Eve" , "CheckersA" ], ] self.assertTableData("People", cols="subset", data=self.people_data) self.assertTableData("Games", cols="subset", data=self.games_data) def test_renames_a(self): # Rename Entries.game: affects Games.winner, Games.second, People.Games_Won, # People.PartnerNames, People.partner. out_actions = self.apply_user_action(["RenameColumn", "Entries", "game", "juego"]) self.partner_names = textwrap.dedent( """ games = Entries.lookupRecords(person=$id).juego partners = [e.person for g in games for e in Entries.lookupRecords(juego=g)] return ' '.join(p.name for p in partners if p.id != $id) """) self.partner = textwrap.dedent( """ game = Entries.lookupOne(person=$id).juego next(e.person for e in Entries.lookupRecords(juego=game) if e.person != rec) """).strip() self.assertPartialOutActions(out_actions, { "stored": [ ["RenameColumn", "Entries", "game", "juego"], ["ModifyColumn", "Games", "winner", {"formula": "Entries.lookupOne(juego=$id, rank=1).person"}], ["ModifyColumn", "Games", "second", {"formula": "Entries.lookupOne(juego=$id, rank=2).person"}], ["ModifyColumn", "People", "Games_Won", { "formula": "' '.join(e.juego.name for e in Entries.lookupRecords(person=$id, rank=1))" }], ["ModifyColumn", "People", "PartnerNames", { "formula": self.partner_names }], ["ModifyColumn", "People", "partner", {"formula": self.partner}], ["ModifyColumn", "People", "win", {"formula": "Entries.lookupOne(person=$id, rank=1).juego"}], ["BulkUpdateRecord", "_grist_Tables_column", [8, 5, 6, 12, 13, 14, 16], { "colId": ["juego", "winner", "second", "Games_Won", "PartnerNames", "partner", "win"], "formula": ["", "Entries.lookupOne(juego=$id, rank=1).person", "Entries.lookupOne(juego=$id, rank=2).person", "' '.join(e.juego.name for e in Entries.lookupRecords(person=$id, rank=1))", self.partner_names, self.partner, "Entries.lookupOne(person=$id, rank=1).juego" ] }], ]}) # Verify data to ensure there are no AttributeErrors. self.assertTableData("People", cols="subset", data=self.people_data) self.assertTableData("Games", cols="subset", data=self.games_data) def test_renames_b(self): # Rename Games.name: affects People.Games_Won, Games.win4_game_name # TODO: win4_game_name isn't updated due to astroid avoidance of looking up the same attr on # the same class during inference. out_actions = self.apply_user_action(["RenameColumn", "Games", "name", "nombre"]) self.assertPartialOutActions(out_actions, { "stored": [ ["RenameColumn", "Games", "name", "nombre"], ["ModifyColumn", "People", "Games_Won", { "formula": "' '.join(e.game.nombre for e in Entries.lookupRecords(person=$id, rank=1))" }], ["BulkUpdateRecord", "_grist_Tables_column", [4, 12], { "colId": ["nombre", "Games_Won"], "formula": [ "", "' '.join(e.game.nombre for e in Entries.lookupRecords(person=$id, rank=1))"] }], ["BulkUpdateRecord", "Games", [1, 2, 3, 4], { "win4_game_name": [["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"]] }], ]}) # Fix up things missed due to the TODOs above. self.modify_column("Games", "win4_game_name", formula="$win.win.win.win.nombre") # Verify data to ensure there are no AttributeErrors. _replace_col_name(self.games_data, "name", "nombre") self.assertTableData("People", cols="subset", data=self.people_data) self.assertTableData("Games", cols="subset", data=self.games_data) def test_renames_c(self): # Rename Entries.person: affects People.ParnerNames out_actions = self.apply_user_action(["RenameColumn", "Entries", "person", "persona"]) self.partner_names = textwrap.dedent( """ games = Entries.lookupRecords(persona=$id).game partners = [e.persona for g in games for e in Entries.lookupRecords(game=g)] return ' '.join(p.name for p in partners if p.id != $id) """) self.partner = textwrap.dedent( """ game = Entries.lookupOne(persona=$id).game next(e.persona for e in Entries.lookupRecords(game=game) if e.persona != rec) """).strip() self.assertPartialOutActions(out_actions, { "stored": [ ["RenameColumn", "Entries", "person", "persona"], ["ModifyColumn", "Games", "winner", {"formula": "Entries.lookupOne(game=$id, rank=1).persona"}], ["ModifyColumn", "Games", "second", {"formula": "Entries.lookupOne(game=$id, rank=2).persona"}], ["ModifyColumn", "People", "Games_Won", { "formula": "' '.join(e.game.name for e in Entries.lookupRecords(persona=$id, rank=1))" }], ["ModifyColumn", "People", "PartnerNames", { "formula": self.partner_names }], ["ModifyColumn", "People", "partner", {"formula": self.partner}], ["ModifyColumn", "People", "win", {"formula": "Entries.lookupOne(persona=$id, rank=1).game"}], ["BulkUpdateRecord", "_grist_Tables_column", [9, 5, 6, 12, 13, 14, 16], { "colId": ["persona", "winner", "second", "Games_Won", "PartnerNames", "partner", "win"], "formula": ["", "Entries.lookupOne(game=$id, rank=1).persona", "Entries.lookupOne(game=$id, rank=2).persona", "' '.join(e.game.name for e in Entries.lookupRecords(persona=$id, rank=1))", self.partner_names, self.partner, "Entries.lookupOne(persona=$id, rank=1).game" ] }], ]}) self.assertTableData("People", cols="subset", data=self.people_data) self.assertTableData("Games", cols="subset", data=self.games_data) def test_renames_d(self): # Rename People.name: affects People.N, People.ParnerNames # TODO: win3_person_name ($win.win.win.name) does NOT get updated correctly with astroid # because of a limitation in astroid inference: it refuses to look up the same attr on the # same class during inference (in order to protect against too much recursion). # TODO: PartnerNames does NOT get updated correctly because astroid doesn't infer meanings of # lists very well. out_actions = self.apply_user_action(["RenameColumn", "People", "name", "nombre"]) self.assertPartialOutActions(out_actions, { "stored": [ ["RenameColumn", "People", "name", "nombre"], ["ModifyColumn", "People", "N", {"formula": "$nombre.upper()"}], ["BulkUpdateRecord", "_grist_Tables_column", [2, 11], { "colId": ["nombre", "N"], "formula": ["", "$nombre.upper()"] }], ["BulkUpdateRecord", "Games", [1, 2, 3, 4], { "win3_person_name": [["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"]] }], ["BulkUpdateRecord", "People", [1, 2, 3, 4, 5], { "PartnerNames": [["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"]] }], ]}) # Fix up things missed due to the TODOs above. self.modify_column("Games", "win3_person_name", formula="$win.win.win.nombre") self.modify_column("People", "PartnerNames", formula=self.partner_names.replace("name", "nombre")) _replace_col_name(self.people_data, "name", "nombre") self.assertTableData("People", cols="subset", data=self.people_data) self.assertTableData("Games", cols="subset", data=self.games_data) def test_renames_e(self): # Rename People.partner: affects People.partner4 # TODO: partner4 ($partner.partner.partner.partner) only gets updated partly because of # astroid's avoidance of looking up the same attr on the same class during inference. out_actions = self.apply_user_action(["RenameColumn", "People", "partner", "companero"]) self.assertPartialOutActions(out_actions, { "stored": [ ["RenameColumn", "People", "partner", "companero"], ["ModifyColumn", "People", "partner4", { "formula": "$companero.companero.partner.partner" }], ["BulkUpdateRecord", "_grist_Tables_column", [14, 15], { "colId": ["companero", "partner4"], "formula": [self.partner, "$companero.companero.partner.partner"] }], ["BulkUpdateRecord", "People", [1, 2, 3, 4, 5], { "partner4": [["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"]] }], ]}) # Fix up things missed due to the TODOs above. self.modify_column("People", "partner4", formula="$companero.companero.companero.companero") _replace_col_name(self.people_data, "partner", "companero") self.assertTableData("People", cols="subset", data=self.people_data) self.assertTableData("Games", cols="subset", data=self.games_data) def test_renames_f(self): # Rename People.win -> People.pwin. Make sure only Game.win is not affected. out_actions = self.apply_user_action(["RenameColumn", "People", "win", "pwin"]) self.assertPartialOutActions(out_actions, { "stored": [ ["RenameColumn", "People", "win", "pwin"], ["ModifyColumn", "Games", "win3_person_name", {"formula": "$win.pwin.win.name"}], # TODO: the omission of the 4th win's update is due to the same astroid bug mentioned above. ["ModifyColumn", "Games", "win4_game_name", {"formula": "$win.pwin.win.win.name"}], ["BulkUpdateRecord", "_grist_Tables_column", [16, 18, 19], { "colId": ["pwin", "win3_person_name", "win4_game_name"], "formula": ["Entries.lookupOne(person=$id, rank=1).game", "$win.pwin.win.name", "$win.pwin.win.win.name"]}], ["BulkUpdateRecord", "Games", [1, 2, 3, 4], { "win4_game_name": [["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"], ["E", "AttributeError"]] }], ]}) # Fix up things missed due to the TODOs above. self.modify_column("Games", "win4_game_name", formula="$win.pwin.win.pwin.name") _replace_col_name(self.people_data, "win", "pwin") self.assertTableData("People", cols="subset", data=self.people_data) self.assertTableData("Games", cols="subset", data=self.games_data) def test_renames_g(self): # Rename Games.win -> Games.gwin. out_actions = self.apply_user_action(["RenameColumn", "Games", "win", "gwin"]) self.assertPartialOutActions(out_actions, { "stored": [ ["RenameColumn", "Games", "win", "gwin"], ["ModifyColumn", "Games", "win3_person_name", {"formula": "$gwin.win.gwin.name"}], ["ModifyColumn", "Games", "win4_game_name", {"formula": "$gwin.win.gwin.win.name"}], ["BulkUpdateRecord", "_grist_Tables_column", [17, 18, 19], { "colId": ["gwin", "win3_person_name", "win4_game_name"], "formula": ["$winner", "$gwin.win.gwin.name", "$gwin.win.gwin.win.name"]}], ]}) _replace_col_name(self.games_data, "win", "gwin") self.assertTableData("People", cols="subset", data=self.people_data) self.assertTableData("Games", cols="subset", data=self.games_data) def test_renames_h(self): # Rename Entries -> Entradas. Affects Games.winner, Games.second, People.Games_Won, # People.PartnerNames, People.partner, People.win. out_actions = self.apply_user_action(["RenameTable", "Entries", "Entradas"]) self.partner_names = textwrap.dedent( """ games = Entradas.lookupRecords(person=$id).game partners = [e.person for g in games for e in Entradas.lookupRecords(game=g)] return ' '.join(p.name for p in partners if p.id != $id) """) self.partner = textwrap.dedent( """ game = Entradas.lookupOne(person=$id).game next(e.person for e in Entradas.lookupRecords(game=game) if e.person != rec) """).strip() self.assertPartialOutActions(out_actions, { "stored": [ ["RenameTable", "Entries", "Entradas"], ["UpdateRecord", "_grist_Tables", 3, {"tableId": "Entradas"}], ["ModifyColumn", "Games", "winner", {"formula": "Entradas.lookupOne(game=$id, rank=1).person"}], ["ModifyColumn", "Games", "second", {"formula": "Entradas.lookupOne(game=$id, rank=2).person"}], ["ModifyColumn", "People", "Games_Won", { "formula": "' '.join(e.game.name for e in Entradas.lookupRecords(person=$id, rank=1))" }], ["ModifyColumn", "People", "PartnerNames", { "formula": self.partner_names }], ["ModifyColumn", "People", "partner", {"formula": self.partner}], ["ModifyColumn", "People", "win", {"formula": "Entradas.lookupOne(person=$id, rank=1).game"}], ["BulkUpdateRecord", "_grist_Tables_column", [5, 6, 12, 13, 14, 16], { "formula": [ "Entradas.lookupOne(game=$id, rank=1).person", "Entradas.lookupOne(game=$id, rank=2).person", "' '.join(e.game.name for e in Entradas.lookupRecords(person=$id, rank=1))", self.partner_names, self.partner, "Entradas.lookupOne(person=$id, rank=1).game" ]}], ]}) self.assertTableData("People", cols="subset", data=self.people_data) self.assertTableData("Games", cols="subset", data=self.games_data)