| 121 | | |
|---|
| 122 | | |
|---|
| 123 | | if not 'SKIP_SLOW' in os.environ: |
|---|
| 124 | | class TestConnectionClientStorage (TestConnection): |
|---|
| 125 | | |
|---|
| 126 | | def setUp(self): |
|---|
| 127 | | self.port = 9123 |
|---|
| 128 | | self.server = popen4('python %s --port=%s' % ( |
|---|
| 129 | | run_durus.__file__, self.port)) |
|---|
| 130 | | sleep(3) # wait for bind |
|---|
| 131 | | |
|---|
| 132 | | def tearDown(self): |
|---|
| 133 | | run_durus.stop_durus((DEFAULT_HOST, self.port)) |
|---|
| 134 | | |
|---|
| 135 | | def _get_storage(self): |
|---|
| 136 | | return ClientStorage(port=self.port) |
|---|
| 137 | | |
|---|
| 138 | | def test_check_conflict(self): |
|---|
| 139 | | b = Connection(self._get_storage()) |
|---|
| 140 | | c = Connection(self._get_storage()) |
|---|
| 141 | | rootb = b.get(p64(0)) |
|---|
| 142 | | rootb['b'] = Persistent() |
|---|
| 143 | | rootc = c.get(p64(0)) |
|---|
| 144 | | rootc['c'] = Persistent() |
|---|
| 145 | | c.commit() |
|---|
| 146 | | assert raises(ConflictError, b.commit) |
|---|
| 147 | | assert raises(KeyError, rootb.__getitem__, 'c') |
|---|
| 148 | | transaction_serial = b.transaction_serial |
|---|
| 149 | | b.abort() |
|---|
| 150 | | assert b.get_transaction_serial() > transaction_serial |
|---|
| 151 | | assert rootb._p_is_ghost() |
|---|
| 152 | | rootc['d'] = Persistent() |
|---|
| 153 | | c.commit() |
|---|
| 154 | | rootb['d'] |
|---|
| 155 | | |
|---|
| 156 | | def test_check_fine_conflict(self): |
|---|
| 157 | | c1 = Connection(self._get_storage()) |
|---|
| 158 | | c2 = Connection(self._get_storage()) |
|---|
| 159 | | c1.get_root()['A'] = Persistent() |
|---|
| 160 | | c1.get_root()['A'].a = 1 |
|---|
| 161 | | c1.get_root()['B'] = Persistent() |
|---|
| 162 | | c1.commit() |
|---|
| 163 | | c2.abort() |
|---|
| 164 | | # c1 has A loaded. |
|---|
| 165 | | assert not c1.get_root()['A']._p_is_ghost() |
|---|
| 166 | | c1.get_root()['B'].b = 1 |
|---|
| 167 | | c2.get_root()['A'].a = 2 |
|---|
| 168 | | c2.commit() |
|---|
| 169 | | # Even though A has been changed by c2, |
|---|
| 170 | | # c1 has not accessed an attribute of A since |
|---|
| 171 | | # the last c1.commit(), so we don't want a ConflictError. |
|---|
| 172 | | c1.commit() |
|---|
| 173 | | assert not c1.get_root()['A']._p_is_ghost() |
|---|
| 174 | | c1.get_root()['A'].a # accessed! |
|---|
| 175 | | c1.get_root()['B'].b = 1 |
|---|
| 176 | | c2.get_root()['A'].a = 2 |
|---|
| 177 | | c2.commit() |
|---|
| 178 | | assert raises(ConflictError, c1.commit) |
|---|
| 179 | | |
|---|
| 180 | | def _scenario(self): |
|---|
| 181 | | c1 = Connection(self._get_storage()) |
|---|
| 182 | | c2 = Connection(self._get_storage()) |
|---|
| 183 | | c1.get_root()['A'] = Persistent() |
|---|
| 184 | | c1.get_root()['B'] = Persistent() |
|---|
| 185 | | c1.get_root()['A'].a = 1 |
|---|
| 186 | | c1.commit() |
|---|
| 187 | | c2.abort() |
|---|
| 188 | | c1.cache.recent_objects.discard(c1.get_root()['A']) |
|---|
| 189 | | # Imagine c1 has been running for a while, and |
|---|
| 190 | | # cache management, for example, has caused the |
|---|
| 191 | | # cache reference to be weak. |
|---|
| 192 | | return c1, c2 |
|---|
| 193 | | |
|---|
| 194 | | def test_conflict_from_invalid_removable_previously_accessed(self): |
|---|
| 195 | | c1, c2 = self._scenario() |
|---|
| 196 | | A = c1.get_root()['A'] |
|---|
| 197 | | A.a # access A in c1. This will lead to conflict. |
|---|
| 198 | | A_oid = A._p_oid |
|---|
| 199 | | assert A in c1.cache.recent_objects |
|---|
| 200 | | A = None # forget about it |
|---|
| 201 | | print |
|---|
| 202 | | # Lose the reference to A. |
|---|
| 203 | | c1.get_root()._p_set_status_ghost() |
|---|
| 204 | | # Commit a new A in c2. |
|---|
| 205 | | c2.get_root()['A'].a = 2 |
|---|
| 206 | | c2.commit() |
|---|
| 207 | | c1.get_root()['B'].b = 1 # touch B in c1 |
|---|
| 208 | | # Conflict because A has been accessed in c1, but it is invalid. |
|---|
| 209 | | assert raises(ConflictError, c1.commit) |
|---|
| 210 | | |
|---|
| 211 | | def test_no_conflict_from_invalid_removable_not_previously_accessed(self): |
|---|
| 212 | | c1, c2 = self._scenario() |
|---|
| 213 | | c1.get_root()._p_set_status_ghost() |
|---|
| 214 | | # Commit a new A in c2. |
|---|
| 215 | | c2.get_root()['A'].a = 2 |
|---|
| 216 | | c2.commit() |
|---|
| 217 | | c1.get_root()['B'].b = 1 # touch B in c1 |
|---|
| 218 | | # A was not accessed before the reference was lost, |
|---|
| 219 | | # so there is no conflict. |
|---|
| 220 | | c1.commit() |
|---|
| 221 | | |
|---|
| 222 | | def test_check_persistentbase_refs(self): |
|---|
| 223 | | refs = getattr(sys, 'gettotalrefcount', None) |
|---|
| 224 | | if refs is None: |
|---|
| 225 | | return |
|---|
| 226 | | before = 0 |
|---|
| 227 | | after = 0 |
|---|
| 228 | | before = refs() |
|---|
| 229 | | PersistentBase() |
|---|
| 230 | | after = refs() |
|---|
| 231 | | assert after - before == 0, after - before |
|---|
| 232 | | |
|---|
| 233 | | def test_check_connectionbase_refs(self): |
|---|
| 234 | | refs = getattr(sys, 'gettotalrefcount', None) |
|---|
| 235 | | if refs is None: |
|---|
| 236 | | return |
|---|
| 237 | | before = 0 |
|---|
| 238 | | after = 0 |
|---|
| 239 | | before = refs() |
|---|
| 240 | | ConnectionBase() |
|---|
| 241 | | after = refs() |
|---|
| 242 | | assert after - before == 0, after - before |
|---|
| 243 | | |
|---|
| 244 | | |
|---|