|
34 | 34 | import org.apache.lucene.store.Directory; |
35 | 35 | import org.apache.lucene.tests.index.RandomIndexWriter; |
36 | 36 | import org.apache.lucene.tests.util.LuceneTestCase; |
| 37 | +import org.apache.lucene.tests.util.RamUsageTester; |
37 | 38 | import org.apache.lucene.tests.util.Rethrow; |
38 | 39 | import org.apache.lucene.tests.util.TestUtil; |
39 | 40 | import org.apache.lucene.tests.util.automaton.AutomatonTestUtil; |
40 | 41 | import org.apache.lucene.util.BytesRef; |
| 42 | +import org.apache.lucene.util.RamUsageEstimator; |
41 | 43 | import org.apache.lucene.util.automaton.Automata; |
42 | 44 | import org.apache.lucene.util.automaton.Automaton; |
43 | 45 | import org.apache.lucene.util.automaton.Operations; |
| 46 | +import org.apache.lucene.util.automaton.RegExp; |
44 | 47 |
|
45 | 48 | public class TestAutomatonQuery extends LuceneTestCase { |
46 | 49 | private Directory directory; |
@@ -252,4 +255,68 @@ public void testBiggishAutomaton() { |
252 | 255 | Collections.sort(terms); |
253 | 256 | new AutomatonQuery(new Term("foo", "bar"), Automata.makeStringUnion(terms)); |
254 | 257 | } |
| 258 | + |
| 259 | + public void testRamBytesUsedDoesNotDoubleCountSharedDeterministicAutomaton() { |
| 260 | + // Same shape as PrefixQuery: an already-deterministic binary automaton passed with |
| 261 | + // isBinary=true. CompiledAutomaton keeps a reference to the same Automaton instance |
| 262 | + // via runAutomaton.automaton; naively summing automaton + compiled would double-count. |
| 263 | + Automaton prefix = PrefixQuery.toAutomaton(new BytesRef("prefix")); |
| 264 | + AutomatonQuery q = new AutomatonQuery(new Term(FN, "prefix"), prefix, true); |
| 265 | + assertSame(prefix, q.getCompiled().automaton); |
| 266 | + assertRamBytesExcludesSharedAutomaton(q, prefix); |
| 267 | + } |
| 268 | + |
| 269 | + public void testRamBytesUsedDoesNotDoubleCountSharedNfaAutomaton() { |
| 270 | + // isBinary=true with a non-deterministic input drives CompiledAutomaton down the NFA |
| 271 | + // path, where nfaRunAutomaton wraps the same Automaton instance. |
| 272 | + Automaton nfa = new Automaton(); |
| 273 | + int start = nfa.createState(); |
| 274 | + int a1 = nfa.createState(); |
| 275 | + int a2 = nfa.createState(); |
| 276 | + nfa.setAccept(a1, true); |
| 277 | + nfa.setAccept(a2, true); |
| 278 | + nfa.addTransition(start, a1, 'a', 'a'); |
| 279 | + nfa.addTransition(start, a2, 'a', 'a'); |
| 280 | + nfa.finishState(); |
| 281 | + assertFalse(nfa.isDeterministic()); |
| 282 | + |
| 283 | + AutomatonQuery q = new AutomatonQuery(new Term(FN, "nfa"), nfa, true); |
| 284 | + assertNull(q.getCompiled().automaton); |
| 285 | + assertTrue(q.getCompiled().sharesAutomaton(nfa)); |
| 286 | + assertRamBytesExcludesSharedAutomaton(q, nfa); |
| 287 | + } |
| 288 | + |
| 289 | + public void testRamBytesUsedIsBinaryFalseCountsOuterAutomaton() { |
| 290 | + // isBinary=false path: CompiledAutomaton converts to UTF-8 internally, so the outer |
| 291 | + // automaton and compiled hold distinct Automaton instances. Both must be counted -- |
| 292 | + // this test guards against a future refactor accidentally dropping the outer bytes. |
| 293 | + Automaton a = |
| 294 | + Operations.determinize(new RegExp("abc.*").toAutomaton(), DEFAULT_DETERMINIZE_WORK_LIMIT); |
| 295 | + AutomatonQuery q = new AutomatonQuery(new Term(FN, "regex"), a); |
| 296 | + assertFalse(q.getCompiled().sharesAutomaton(a)); |
| 297 | + long reported = q.ramBytesUsed(); |
| 298 | + assertTrue( |
| 299 | + "outer automaton must still contribute on the isBinary=false path", |
| 300 | + reported >= a.ramBytesUsed()); |
| 301 | + long actual = RamUsageTester.ramUsed(q); |
| 302 | + assertEquals((double) actual, (double) reported, (double) actual * 0.10); |
| 303 | + } |
| 304 | + |
| 305 | + // Asserts that ramBytesUsed() does not include the shared Automaton twice. |
| 306 | + // Reported bytes must equal (shallow AutomatonQuery + term + compiled), i.e., the |
| 307 | + // shared automaton is accounted for only once (via compiled). If the bug were |
| 308 | + // present, reported would be inflated by exactly sharedAutomaton.ramBytesUsed(). |
| 309 | + private static void assertRamBytesExcludesSharedAutomaton( |
| 310 | + AutomatonQuery q, Automaton sharedAutomaton) { |
| 311 | + long expected = |
| 312 | + RamUsageEstimator.shallowSizeOfInstance(AutomatonQuery.class) |
| 313 | + + q.term.ramBytesUsed() |
| 314 | + + q.getCompiled().ramBytesUsed(); |
| 315 | + assertEquals( |
| 316 | + "shared Automaton must not be counted twice (would over-report by " |
| 317 | + + sharedAutomaton.ramBytesUsed() |
| 318 | + + " bytes)", |
| 319 | + expected, |
| 320 | + q.ramBytesUsed()); |
| 321 | + } |
255 | 322 | } |
0 commit comments