Пример #1
0
    def test_failure_after_side_effect(self):
        # Verify that when a formula fails after a side-effect, the effect is reverted.
        self.load_sample(self.sample)

        formula = 'Schools.lookupOrAddDerived(city="TESTCITY")\nraise Exception("test-error")'
        out_actions = self.apply_user_action(
            ['AddColumn', 'Address', "A", {
                'formula': formula
            }])
        self.assertPartialOutActions(
            out_actions,
            {
                "stored": [
                    [
                        "AddColumn", "Address", "A", {
                            "formula": formula,
                            "isFormula": True,
                            "type": "Any"
                        }
                    ],
                    [
                        "AddRecord", "_grist_Tables_column", 13, {
                            "colId": "A",
                            "formula": formula,
                            "isFormula": True,
                            "label": "A",
                            "parentId": 1,
                            "parentPos": 4.0,
                            "type": "Any",
                            "widgetOptions": ""
                        }
                    ],
                    [
                        "BulkUpdateRecord", "Address", [21, 22], {
                            "A": [["E", "Exception"], ["E", "Exception"]]
                        }
                    ],
                    # The thing to note  here is that while lookupOrAddDerived() should have added a row to
                    # Schools, the Exception negated it, and there is no action to add that row.
                ]
            })

        # Check that data is as expected: no new records in Schools, one new column in Address.
        self.assertTableData('Schools',
                             cols="all",
                             data=self.schools_table_data)
        self.assertTableData(
            'Address',
            cols="all",
            data=[
                ["id", "city", "state", "amount", "A"],
                [
                    21, "New York", "NY", 1,
                    objtypes.RaisedException(Exception())
                ],
                [22, "Albany", "NY", 2,
                 objtypes.RaisedException(Exception())],
            ])
Пример #2
0
    def test_loop(self):
        sample = testutil.parse_test_sample({
            "SCHEMA": [[
                1, "Table1",
                [
                    [31, "A", "Numeric", False, "", "", ""],
                    [31, "B", "Numeric", True, "$C", "", ""],
                    [32, "C", "Numeric", True, "$B", "", ""],
                ]
            ]],
            "DATA": {
                "Table1": [
                    ["id", "A"],
                    [1, 1],
                    [2, 2],
                    [3, 3],
                ]
            }
        })

        self.load_sample(sample)
        circle = objtypes.RaisedException(depend.CircularRefError())
        self.assertTableData('Table1',
                             data=[
                                 ['id', 'A', 'B', 'C'],
                                 [1, 1, circle, circle],
                                 [2, 2, circle, circle],
                                 [3, 3, circle, circle],
                             ])
Пример #3
0
    def test_cycle(self):
        sample = testutil.parse_test_sample({
            "SCHEMA": [[
                1, "Table1",
                [
                    [30, "A", "Numeric", False, "", "", ""],
                    [31, "Principal", "Numeric", True, "$Interest", "", ""],
                    [32, "Interest", "Numeric", True, "$Principal", "", ""],
                    [33, "A2", "Numeric", True, "$A", "", ""],
                ]
            ]],
            "DATA": {
                "Table1": [
                    ["id", "A"],
                    [1, 1],
                    [2, 2],
                    [3, 3],
                ]
            }
        })

        self.load_sample(sample)
        circle = objtypes.RaisedException(depend.CircularRefError())
        self.assertTableData('Table1',
                             data=[
                                 ['id', 'A', 'Principal', 'Interest', 'A2'],
                                 [1, 1, circle, circle, 1],
                                 [2, 2, circle, circle, 2],
                                 [3, 3, circle, circle, 3],
                             ])
Пример #4
0
    def test_cycle_and_reference(self):
        sample = testutil.parse_test_sample({
            "SCHEMA": [
                [
                    2, "ATable",
                    [
                        [32, "A", "Ref:ZTable", False, "", "", ""],
                        [33, "B", "Numeric", True, "$A.B", "", ""],
                    ]
                ],
                [
                    1, "ZTable",
                    [
                        [31, "A", "Numeric", False, "", "", ""],
                        [31, "B", "Numeric", True, "$B", "", ""],
                    ]
                ],
            ],
            "DATA": {
                "ATable": [
                    ["id", "A"],
                    [1, 1],
                    [2, 2],
                    [3, 3],
                ],
                "ZTable": [
                    ["id", "A"],
                    [1, 6],
                    [2, 7],
                    [3, 8],
                ]
            }
        })

        self.load_sample(sample)
        circle = objtypes.RaisedException(depend.CircularRefError())
        self.assertTableData('ATable',
                             data=[
                                 ['id', 'A', 'B'],
                                 [1, 1, circle],
                                 [2, 2, circle],
                                 [3, 3, circle],
                             ])
        self.assertTableData('ZTable',
                             data=[
                                 ['id', 'A', 'B'],
                                 [1, 6, circle],
                                 [2, 7, circle],
                                 [3, 8, circle],
                             ])
Пример #5
0
    def test_cumulative_formula_with_references(self):
        top = 100
        formula = "max($Prev.Principal + $Prev.Interest, 1000)"
        sample = testutil.parse_test_sample({
            "SCHEMA": [[
                1, "Table1",
                [
                    [41, "Prev", "Ref:Table1", True, "$id - 1", "", ""],
                    [42, "Principal", "Numeric", True, formula, "", ""],
                    [
                        43, "Interest", "Numeric", True,
                        "int($Principal * 0.1)", "", ""
                    ],
                ]
            ],
                       [
                           2, "Readout",
                           [
                               [
                                   46, "LastPrincipal", "Numeric", True,
                                   "Table1.lookupOne(id=%d).Principal" % top,
                                   "", ""
                               ],
                           ]
                       ]],
            "DATA": {
                "Table1": [["id"]] + [[r] for r in range(1, top + 1)],
                "Readout": [["id"], [1]],
            }
        })

        self.load_sample(sample)
        self.assertTableData('Readout',
                             data=[
                                 ['id', 'LastPrincipal'],
                                 [1, 12494908.0],
                             ])

        self.modify_column("Table1",
                           "Prev",
                           formula="$id - 1 if $id > 1 else 100")
        self.assertTableData(
            'Readout',
            data=[
                ['id', 'LastPrincipal'],
                [1, objtypes.RaisedException(depend.CircularRefError())],
            ])
Пример #6
0
    def test_attribute_error(self):
        sample = testutil.parse_test_sample({
            "SCHEMA": [[
                1, "AttrTest",
                [
                    [30, "A", "Numeric", False, "", "", ""],
                    [31, "B", "Numeric", True, "$AA", "", ""],
                    [32, "C", "Numeric", True, "$B", "", ""],
                ]
            ]],
            "DATA": {
                "AttrTest": [
                    ["id", "A"],
                    [1, 1],
                    [2, 2],
                ]
            }
        })

        self.load_sample(sample)
        errVal = objtypes.RaisedException(AttributeError())
        self.assertTableData('AttrTest',
                             data=[
                                 ['id', 'A', 'B', 'C'],
                                 [1, 1, errVal, errVal],
                                 [2, 2, errVal, errVal],
                             ])

        self.assertFormulaError(
            self.engine.get_formula_error('AttrTest', 'B', 1), AttributeError,
            "Table 'AttrTest' has no column 'AA'",
            r"AttributeError: Table 'AttrTest' has no column 'AA'")
        cell_error = self.engine.get_formula_error('AttrTest', 'C', 1)
        self.assertFormulaError(
            cell_error, objtypes.CellError,
            "AttributeError in referenced cell AttrTest[1].B",
            r"CellError: AttributeError in referenced cell AttrTest\[1\].B")
        self.assertEqual(objtypes.encode_object(cell_error), [
            'E', 'AttributeError', "Table 'AttrTest' has no column 'AA'\n"
            "(in referenced cell AttrTest[1].B)", cell_error.details
        ])
Пример #7
0
 def test_record_bad_calls(self):
     self.load_sample(testsamples.sample_students)
     self.add_column("Schools", "Foo", formula='repr(RECORD($name))')
     self.assertPartialData("Schools", ["id", "Foo"], [
         [1, objtypes.RaisedException(ValueError())],
         [2, objtypes.RaisedException(ValueError())],
         [3, objtypes.RaisedException(ValueError())],
         [4, objtypes.RaisedException(ValueError())],
     ])
     self.modify_column("Schools",
                        "Foo",
                        formula='repr(RECORD([rec] if $id == 2 else $id))')
     self.assertPartialData("Schools", ["id", "Foo"], [
         [1, objtypes.RaisedException(ValueError())],
         [2, "[{'address': Address[12], 'id': 2, 'name': 'Columbia'}]"],
         [3, objtypes.RaisedException(ValueError())],
         [4, objtypes.RaisedException(ValueError())],
     ])
     self.assertEqual(
         self.engine.get_formula_error('Schools', 'Foo', 1).error.message,
         'RECORD() requires a Record or an iterable of Records')
Пример #8
0
 def test_record_bad_calls(self):
     self.load_sample(testsamples.sample_students)
     self.add_column("Schools", "Foo", formula='repr(RECORD($name))')
     self.assertPartialData("Schools", ["id", "Foo"], [
         [1, objtypes.RaisedException(ValueError())],
         [2, objtypes.RaisedException(ValueError())],
         [3, objtypes.RaisedException(ValueError())],
         [4, objtypes.RaisedException(ValueError())],
     ])
     self.modify_column(
         "Schools",
         "Foo",
         formula='repr(sorted(RECORD(rec if $id == 2 else $id).items()))')
     self.assertPartialData("Schools", ["id", "Foo"], [
         [1, objtypes.RaisedException(ValueError())],
         [2, "[('address', Address[12]), ('id', 2), ('name', 'Columbia')]"],
         [3, objtypes.RaisedException(ValueError())],
         [4, objtypes.RaisedException(ValueError())],
     ])
     self.assertEqual(
         str(self.engine.get_formula_error('Schools', 'Foo', 1).error),
         'RECORD() requires a Record or an iterable of Records')
    def test_attribute_error(self):
        sample = testutil.parse_test_sample({
            "SCHEMA": [[
                1, "AttrTest",
                [
                    [30, "A", "Numeric", False, "", "", ""],
                    [31, "B", "Numeric", True, "$AA", "", ""],
                    [32, "C", "Numeric", True, "$B", "", ""],
                ]
            ]],
            "DATA": {
                "AttrTest": [
                    ["id", "A"],
                    [1, 1],
                    [2, 2],
                ]
            }
        })

        self.load_sample(sample)
        errVal = objtypes.RaisedException(AttributeError())
        self.assertTableData('AttrTest',
                             data=[
                                 ['id', 'A', 'B', 'C'],
                                 [1, 1, errVal, errVal],
                                 [2, 2, errVal, errVal],
                             ])

        self.assertFormulaError(
            self.engine.get_formula_error('AttrTest', 'B', 1), AttributeError,
            "Table 'AttrTest' has no column 'AA'",
            r"AttributeError: Table 'AttrTest' has no column 'AA'")
        self.assertFormulaError(
            self.engine.get_formula_error('AttrTest', 'C', 1), AttributeError,
            "Table 'AttrTest' has no column 'AA'",
            r"AttributeError: Table 'AttrTest' has no column 'AA'")
Пример #10
0
 def test_catch_all_in_formula(self):
     sample = testutil.parse_test_sample({
         "SCHEMA": [
             [
                 1, "Table1",
                 [
                     [51, "A", "Numeric", False, "", "", ""],
                     [
                         52, "B1", "Numeric", True,
                         "try:\n  return $A+$C\nexcept:\n  return 42", "",
                         ""
                     ],
                     [
                         53, "B2", "Numeric", True,
                         "try:\n  return $D+None\nexcept:\n  return 42", "",
                         ""
                     ],
                     [
                         54, "B3", "Numeric", True,
                         "try:\n  return $A+$B4+$D\nexcept:\n  return 42",
                         "", ""
                     ],
                     [
                         55, "B4", "Numeric", True,
                         "try:\n  return $A+$B3+$D\nexcept:\n  return 42",
                         "", ""
                     ],
                     [
                         56, "B5", "Numeric", True,
                         "try:\n  return $E+1\nexcept:\n  raise Exception('monkeys!')",
                         "", ""
                     ],
                     [
                         56, "B6", "Numeric", True,
                         "try:\n  return $F+1\nexcept Exception as e:\n  e.node = e.row_id = 'monkey'",
                         "", ""
                     ],
                     [57, "C", "Numeric", False, "", "", ""],
                     [58, "D", "Numeric", True, "$A", "", ""],
                     [59, "E", "Numeric", True, "$A", "", ""],
                     [59, "F", "Numeric", True, "$A", "", ""],
                 ]
             ],
         ],
         "DATA": {
             "Table1": [["id", "A", "C"], [1, 1, 2], [2, 20, 10]],
         }
     })
     self.load_sample(sample)
     circle = objtypes.RaisedException(depend.CircularRefError())
     # B4 is a subtle case.  B3 and B4 refer to each other.  B3 is recomputed first,
     # and cells evaluate to a CircularRefError.  Now B3 has a value, so B4 can be
     # evaluated, and results in 42 when addition of an integer and an exception value
     # fails.
     self.assertTableData(
         'Table1',
         data=[
             [
                 'id', 'A', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'C', 'D',
                 'E', 'F'
             ],
             [1, 1, 3, 42, circle, 42, 2, 2, 2, 1, 1, 1],
             [2, 20, 30, 42, circle, 42, 21, 21, 10, 20, 20, 20],
         ])
Пример #11
0
    def test_formula_reading_from_an_errored_formula(self):
        # There was a bug whereby if one formula (call it D) referred to
        # another (call it T), and that other formula was in error, the
        # error values of that second formula would not be passed on the
        # client as a BulkUpdateRecord.  The bug was dependent on order of
        # evaluation of columns.  D would be evaluated first, and evaluate
        # T in a nested way.  When evaluating T, a BulkUpdateRecord would
        # be prepared correctly, and when popping back to evaluate D,
        # the BulkUpdateRecord for D would be prepared correctly, but since
        # D was an error, any nested actions would be reverted (this is
        # logic related to undoing potential side-effects on failure).

        # First, set up a table with a sequence in A, a formula to do cumulative sums in T,
        # and a formula D to copy T.
        formula = "recs = UpdateTest.lookupRecords()\nsum(r.A for r in recs if r.A <= $A)"
        sample = testutil.parse_test_sample({
            "SCHEMA": [[
                1, "UpdateTest",
                [
                    [20, "A", "Numeric", False, "", "", ""],
                    [21, "T", "Numeric", True, formula, "", ""],
                    [22, "D", "Numeric", True, "$T", "", ""],
                ]
            ]],
            "DATA": {
                "UpdateTest": [
                    ["id", "A"],
                    [1, 1],
                    [2, 2],
                    [3, 3],
                ]
            }
        })

        # Check the setup is working correctly.
        self.load_sample(sample)
        self.assertTableData('UpdateTest',
                             data=[
                                 ['id', 'A', 'T', 'D'],
                                 [1, 1., 1., 1.],
                                 [2, 2., 3., 3.],
                                 [3, 3., 6., 6.],
                             ])

        # Now rename the data column.  This rename results in a partial
        # update to the T formula that leaves it broken (not all the As are caught).
        out_actions = self.apply_user_action(
            ["RenameColumn", "UpdateTest", "A", "AA"])

        # Make sure the we have bulk updates for both T and D, and not just D.
        err = ["E", "AttributeError"]
        self.assertPartialOutActions(
            out_actions, {
                "stored": [
                    ["RenameColumn", "UpdateTest", "A", "AA"],
                    [
                        "ModifyColumn", "UpdateTest", "T", {
                            "formula":
                            "recs = UpdateTest.lookupRecords()\nsum(r.A for r in recs if r.A <= $AA)"
                        }
                    ],
                    [
                        "BulkUpdateRecord", "_grist_Tables_column", [20, 21], {
                            "colId": ["AA", "T"],
                            "formula": [
                                "",
                                "recs = UpdateTest.lookupRecords()\nsum(r.A for r in recs if r.A <= $AA)"
                            ]
                        }
                    ],
                    [
                        "BulkUpdateRecord", "UpdateTest", [1, 2, 3], {
                            "D": [err, err, err]
                        }
                    ],
                    [
                        "BulkUpdateRecord", "UpdateTest", [1, 2, 3], {
                            "T": [err, err, err]
                        }
                    ],
                ]
            })

        # Make sure the table is in the correct state.
        errVal = objtypes.RaisedException(AttributeError())
        self.assertTableData('UpdateTest',
                             data=[
                                 ['id', 'AA', 'T', 'D'],
                                 [1, 1., errVal, errVal],
                                 [2, 2., errVal, errVal],
                                 [3, 3., errVal, errVal],
                             ])
Пример #12
0
    def test_lookup_state(self):
        # Bug https://phab.getgrist.com/T297 was caused by lookup maps getting corrupted while
        # re-evaluating a formula for the sake of getting error details. This test case reproduces the
        # bug in the old code and verifies that it is fixed.
        sample = testutil.parse_test_sample({
            "SCHEMA": [[
                1, "LookupTest",
                [
                    [11, "A", "Numeric", False, "", "", ""],
                    [
                        12, "B", "Text", True,
                        "LookupTest.lookupOne(A=2).x.upper()", "", ""
                    ],
                ]
            ]],
            "DATA": {
                "LookupTest": [
                    ["id", "A"],
                    [7, 2],
                ]
            }
        })

        self.load_sample(sample)
        self.assertTableData(
            'LookupTest',
            data=[
                ['id', 'A', 'B'],
                [7, 2., objtypes.RaisedException(AttributeError())],
            ])

        # Updating a dependency shouldn't cause problems.
        self.update_record('LookupTest', 7, A=3)
        self.assertTableData(
            'LookupTest',
            data=[
                ['id', 'A', 'B'],
                [7, 3., objtypes.RaisedException(AttributeError())],
            ])

        # Fetch the error details.
        self.assertFormulaError(
            self.engine.get_formula_error('LookupTest', 'B', 7),
            AttributeError, "Table 'LookupTest' has no column 'x'")

        # Updating a dependency after the fetch used to cause the error
        # "AttributeError: 'Table' object has no attribute 'col_id'". Check that it's fixed.
        self.update_record('LookupTest', 7,
                           A=2)  # Should NOT raise an exception.
        self.assertTableData(
            'LookupTest',
            data=[
                ['id', 'A', 'B'],
                [7, 2., objtypes.RaisedException(AttributeError())],
            ])

        # Add the column that will fix the attribute error.
        self.add_column('LookupTest', 'x', type='Text')
        self.assertTableData('LookupTest',
                             data=[
                                 ['id', 'A', 'x', 'B'],
                                 [7, 2., '', ''],
                             ])

        # And check that the dependency still works and is recomputed.
        self.update_record('LookupTest', 7, x='hello')
        self.assertTableData('LookupTest',
                             data=[
                                 ['id', 'A', 'x', 'B'],
                                 [7, 2., 'hello', 'HELLO'],
                             ])
        self.update_record('LookupTest', 7, A=3)
        self.assertTableData('LookupTest',
                             data=[
                                 ['id', 'A', 'x', 'B'],
                                 [7, 3., 'hello', ''],
                             ])