1 /* 2 * Copyright (C) 2009 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.collect; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import com.google.common.annotations.GwtCompatible; 22 import com.google.common.annotations.GwtIncompatible; 23 import com.google.common.collect.Table.Cell; 24 import com.google.common.testing.CollectorTester; 25 import com.google.common.testing.SerializableTester; 26 import java.util.stream.Collector; 27 28 /** 29 * Tests common methods in {@link ImmutableTable} 30 * 31 * @author Gregory Kick 32 */ 33 @GwtCompatible(emulated = true) 34 public class ImmutableTableTest extends AbstractTableReadTest { 35 @Override create(Object... data)36 protected Table<String, Integer, Character> create(Object... data) { 37 ImmutableTable.Builder<String, Integer, Character> builder = ImmutableTable.builder(); 38 for (int i = 0; i < data.length; i = i + 3) { 39 builder.put((String) data[i], (Integer) data[i + 1], (Character) data[i + 2]); 40 } 41 return builder.build(); 42 } 43 44 // The bulk of the toImmutableTable tests can be found in TableCollectorsTest. 45 // This gives minimal coverage to the forwarding functions testToImmutableTableSanityTest()46 public void testToImmutableTableSanityTest() { 47 Collector<Cell<String, String, Integer>, ?, ImmutableTable<String, String, Integer>> collector = 48 TableCollectors.toImmutableTable(Cell::getRowKey, Cell::getColumnKey, Cell::getValue); 49 CollectorTester.of(collector) 50 .expectCollects(ImmutableTable.of()) 51 .expectCollects(ImmutableTable.of("one", "uno", 1), Tables.immutableCell("one", "uno", 1)); 52 } 53 testToImmutableTableMergingSanityTest()54 public void testToImmutableTableMergingSanityTest() { 55 Collector<Cell<String, String, Integer>, ?, ImmutableTable<String, String, Integer>> collector = 56 TableCollectors.toImmutableTable( 57 Cell::getRowKey, Cell::getColumnKey, Cell::getValue, Integer::sum); 58 CollectorTester.of(collector) 59 .expectCollects(ImmutableTable.of()) 60 .expectCollects( 61 ImmutableTable.of("one", "uno", 3), 62 Tables.immutableCell("one", "uno", 1), 63 Tables.immutableCell("one", "uno", 2)); 64 } 65 testBuilder()66 public void testBuilder() { 67 ImmutableTable.Builder<Character, Integer, String> builder = new ImmutableTable.Builder<>(); 68 assertEquals(ImmutableTable.of(), builder.build()); 69 assertEquals(ImmutableTable.of('a', 1, "foo"), builder.put('a', 1, "foo").build()); 70 Table<Character, Integer, String> expectedTable = HashBasedTable.create(); 71 expectedTable.put('a', 1, "foo"); 72 expectedTable.put('b', 1, "bar"); 73 expectedTable.put('a', 2, "baz"); 74 Table<Character, Integer, String> otherTable = HashBasedTable.create(); 75 otherTable.put('b', 1, "bar"); 76 otherTable.put('a', 2, "baz"); 77 assertEquals(expectedTable, builder.putAll(otherTable).build()); 78 } 79 testBuilder_withImmutableCell()80 public void testBuilder_withImmutableCell() { 81 ImmutableTable.Builder<Character, Integer, String> builder = new ImmutableTable.Builder<>(); 82 assertEquals( 83 ImmutableTable.of('a', 1, "foo"), builder.put(Tables.immutableCell('a', 1, "foo")).build()); 84 } 85 testBuilder_withImmutableCellAndNullContents()86 public void testBuilder_withImmutableCellAndNullContents() { 87 ImmutableTable.Builder<Character, Integer, String> builder = new ImmutableTable.Builder<>(); 88 try { 89 builder.put(Tables.immutableCell((Character) null, 1, "foo")); 90 fail(); 91 } catch (NullPointerException e) { 92 // success 93 } 94 try { 95 builder.put(Tables.immutableCell('a', (Integer) null, "foo")); 96 fail(); 97 } catch (NullPointerException e) { 98 // success 99 } 100 try { 101 builder.put(Tables.immutableCell('a', 1, (String) null)); 102 fail(); 103 } catch (NullPointerException e) { 104 // success 105 } 106 } 107 108 private static class StringHolder { 109 String string; 110 } 111 testBuilder_withMutableCell()112 public void testBuilder_withMutableCell() { 113 ImmutableTable.Builder<Character, Integer, String> builder = new ImmutableTable.Builder<>(); 114 115 final StringHolder holder = new StringHolder(); 116 holder.string = "foo"; 117 Table.Cell<Character, Integer, String> mutableCell = 118 new Tables.AbstractCell<Character, Integer, String>() { 119 @Override 120 public Character getRowKey() { 121 return 'K'; 122 } 123 124 @Override 125 public Integer getColumnKey() { 126 return 42; 127 } 128 129 @Override 130 public String getValue() { 131 return holder.string; 132 } 133 }; 134 135 // Add the mutable cell to the builder 136 builder.put(mutableCell); 137 138 // Mutate the value 139 holder.string = "bar"; 140 141 // Make sure it uses the original value. 142 assertEquals(ImmutableTable.of('K', 42, "foo"), builder.build()); 143 } 144 testBuilder_noDuplicates()145 public void testBuilder_noDuplicates() { 146 ImmutableTable.Builder<Character, Integer, String> builder = 147 new ImmutableTable.Builder<Character, Integer, String>() 148 .put('a', 1, "foo") 149 .put('a', 1, "bar"); 150 try { 151 builder.build(); 152 fail(); 153 } catch (IllegalArgumentException e) { 154 // success 155 } 156 } 157 testBuilder_noNulls()158 public void testBuilder_noNulls() { 159 ImmutableTable.Builder<Character, Integer, String> builder = new ImmutableTable.Builder<>(); 160 try { 161 builder.put(null, 1, "foo"); 162 fail(); 163 } catch (NullPointerException e) { 164 // success 165 } 166 try { 167 builder.put('a', null, "foo"); 168 fail(); 169 } catch (NullPointerException e) { 170 // success 171 } 172 try { 173 builder.put('a', 1, null); 174 fail(); 175 } catch (NullPointerException e) { 176 // success 177 } 178 } 179 validateTableCopies(Table<R, C, V> original)180 private static <R, C, V> void validateTableCopies(Table<R, C, V> original) { 181 Table<R, C, V> copy = ImmutableTable.copyOf(original); 182 assertEquals(original, copy); 183 validateViewOrdering(original, copy); 184 185 Table<R, C, V> built = ImmutableTable.<R, C, V>builder().putAll(original).build(); 186 assertEquals(original, built); 187 validateViewOrdering(original, built); 188 } 189 validateViewOrdering(Table<R, C, V> original, Table<R, C, V> copy)190 private static <R, C, V> void validateViewOrdering(Table<R, C, V> original, Table<R, C, V> copy) { 191 assertThat(copy.cellSet()).containsExactlyElementsIn(original.cellSet()).inOrder(); 192 assertThat(copy.rowKeySet()).containsExactlyElementsIn(original.rowKeySet()).inOrder(); 193 assertThat(copy.values()).containsExactlyElementsIn(original.values()).inOrder(); 194 } 195 testCopyOf()196 public void testCopyOf() { 197 Table<Character, Integer, String> table = TreeBasedTable.create(); 198 validateTableCopies(table); 199 table.put('b', 2, "foo"); 200 validateTableCopies(table); 201 table.put('b', 1, "bar"); 202 table.put('a', 2, "baz"); 203 validateTableCopies(table); 204 // Even though rowKeySet, columnKeySet, and cellSet have the same 205 // iteration ordering, row has an inconsistent ordering. 206 assertThat(table.row('b').keySet()).containsExactly(1, 2).inOrder(); 207 assertThat(ImmutableTable.copyOf(table).row('b').keySet()).containsExactly(2, 1).inOrder(); 208 } 209 testCopyOfSparse()210 public void testCopyOfSparse() { 211 Table<Character, Integer, String> table = TreeBasedTable.create(); 212 table.put('x', 2, "foo"); 213 table.put('r', 1, "bar"); 214 table.put('c', 3, "baz"); 215 table.put('b', 7, "cat"); 216 table.put('e', 5, "dog"); 217 table.put('c', 0, "axe"); 218 table.put('e', 3, "tub"); 219 table.put('r', 4, "foo"); 220 table.put('x', 5, "bar"); 221 validateTableCopies(table); 222 } 223 testCopyOfDense()224 public void testCopyOfDense() { 225 Table<Character, Integer, String> table = TreeBasedTable.create(); 226 table.put('c', 3, "foo"); 227 table.put('c', 2, "bar"); 228 table.put('c', 1, "baz"); 229 table.put('b', 3, "cat"); 230 table.put('b', 1, "dog"); 231 table.put('a', 3, "foo"); 232 table.put('a', 2, "bar"); 233 table.put('a', 1, "baz"); 234 validateTableCopies(table); 235 } 236 testBuilder_orderRowsAndColumnsBy_putAll()237 public void testBuilder_orderRowsAndColumnsBy_putAll() { 238 Table<Character, Integer, String> table = HashBasedTable.create(); 239 table.put('b', 2, "foo"); 240 table.put('b', 1, "bar"); 241 table.put('a', 2, "baz"); 242 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 243 Table<Character, Integer, String> copy = 244 builder 245 .orderRowsBy(Ordering.natural()) 246 .orderColumnsBy(Ordering.natural()) 247 .putAll(table) 248 .build(); 249 assertThat(copy.rowKeySet()).containsExactly('a', 'b').inOrder(); 250 assertThat(copy.columnKeySet()).containsExactly(1, 2).inOrder(); 251 assertThat(copy.values()).containsExactly("baz", "bar", "foo").inOrder(); 252 assertThat(copy.row('b').keySet()).containsExactly(1, 2).inOrder(); 253 } 254 testBuilder_orderRowsAndColumnsBy_sparse()255 public void testBuilder_orderRowsAndColumnsBy_sparse() { 256 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 257 builder.orderRowsBy(Ordering.natural()); 258 builder.orderColumnsBy(Ordering.natural()); 259 builder.put('x', 2, "foo"); 260 builder.put('r', 1, "bar"); 261 builder.put('c', 3, "baz"); 262 builder.put('b', 7, "cat"); 263 builder.put('e', 5, "dog"); 264 builder.put('c', 0, "axe"); 265 builder.put('e', 3, "tub"); 266 builder.put('r', 4, "foo"); 267 builder.put('x', 5, "bar"); 268 Table<Character, Integer, String> table = builder.build(); 269 assertThat(table.rowKeySet()).containsExactly('b', 'c', 'e', 'r', 'x').inOrder(); 270 assertThat(table.columnKeySet()).containsExactly(0, 1, 2, 3, 4, 5, 7).inOrder(); 271 assertThat(table.values()) 272 .containsExactly("cat", "axe", "baz", "tub", "dog", "bar", "foo", "foo", "bar") 273 .inOrder(); 274 assertThat(table.row('c').keySet()).containsExactly(0, 3).inOrder(); 275 assertThat(table.column(5).keySet()).containsExactly('e', 'x').inOrder(); 276 } 277 testBuilder_orderRowsAndColumnsBy_dense()278 public void testBuilder_orderRowsAndColumnsBy_dense() { 279 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 280 builder.orderRowsBy(Ordering.natural()); 281 builder.orderColumnsBy(Ordering.natural()); 282 builder.put('c', 3, "foo"); 283 builder.put('c', 2, "bar"); 284 builder.put('c', 1, "baz"); 285 builder.put('b', 3, "cat"); 286 builder.put('b', 1, "dog"); 287 builder.put('a', 3, "foo"); 288 builder.put('a', 2, "bar"); 289 builder.put('a', 1, "baz"); 290 Table<Character, Integer, String> table = builder.build(); 291 assertThat(table.rowKeySet()).containsExactly('a', 'b', 'c').inOrder(); 292 assertThat(table.columnKeySet()).containsExactly(1, 2, 3).inOrder(); 293 assertThat(table.values()) 294 .containsExactly("baz", "bar", "foo", "dog", "cat", "baz", "bar", "foo") 295 .inOrder(); 296 assertThat(table.row('c').keySet()).containsExactly(1, 2, 3).inOrder(); 297 assertThat(table.column(1).keySet()).containsExactly('a', 'b', 'c').inOrder(); 298 } 299 testBuilder_orderRowsBy_sparse()300 public void testBuilder_orderRowsBy_sparse() { 301 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 302 builder.orderRowsBy(Ordering.natural()); 303 builder.put('x', 2, "foo"); 304 builder.put('r', 1, "bar"); 305 builder.put('c', 3, "baz"); 306 builder.put('b', 7, "cat"); 307 builder.put('e', 5, "dog"); 308 builder.put('c', 0, "axe"); 309 builder.put('e', 3, "tub"); 310 builder.put('r', 4, "foo"); 311 builder.put('x', 5, "bar"); 312 Table<Character, Integer, String> table = builder.build(); 313 assertThat(table.rowKeySet()).containsExactly('b', 'c', 'e', 'r', 'x').inOrder(); 314 assertThat(table.column(5).keySet()).containsExactly('e', 'x').inOrder(); 315 } 316 testBuilder_orderRowsBy_dense()317 public void testBuilder_orderRowsBy_dense() { 318 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 319 builder.orderRowsBy(Ordering.natural()); 320 builder.put('c', 3, "foo"); 321 builder.put('c', 2, "bar"); 322 builder.put('c', 1, "baz"); 323 builder.put('b', 3, "cat"); 324 builder.put('b', 1, "dog"); 325 builder.put('a', 3, "foo"); 326 builder.put('a', 2, "bar"); 327 builder.put('a', 1, "baz"); 328 Table<Character, Integer, String> table = builder.build(); 329 assertThat(table.rowKeySet()).containsExactly('a', 'b', 'c').inOrder(); 330 assertThat(table.column(1).keySet()).containsExactly('a', 'b', 'c').inOrder(); 331 } 332 testBuilder_orderColumnsBy_sparse()333 public void testBuilder_orderColumnsBy_sparse() { 334 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 335 builder.orderColumnsBy(Ordering.natural()); 336 builder.put('x', 2, "foo"); 337 builder.put('r', 1, "bar"); 338 builder.put('c', 3, "baz"); 339 builder.put('b', 7, "cat"); 340 builder.put('e', 5, "dog"); 341 builder.put('c', 0, "axe"); 342 builder.put('e', 3, "tub"); 343 builder.put('r', 4, "foo"); 344 builder.put('x', 5, "bar"); 345 Table<Character, Integer, String> table = builder.build(); 346 assertThat(table.columnKeySet()).containsExactly(0, 1, 2, 3, 4, 5, 7).inOrder(); 347 assertThat(table.row('c').keySet()).containsExactly(0, 3).inOrder(); 348 } 349 testBuilder_orderColumnsBy_dense()350 public void testBuilder_orderColumnsBy_dense() { 351 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 352 builder.orderColumnsBy(Ordering.natural()); 353 builder.put('c', 3, "foo"); 354 builder.put('c', 2, "bar"); 355 builder.put('c', 1, "baz"); 356 builder.put('b', 3, "cat"); 357 builder.put('b', 1, "dog"); 358 builder.put('a', 3, "foo"); 359 builder.put('a', 2, "bar"); 360 builder.put('a', 1, "baz"); 361 Table<Character, Integer, String> table = builder.build(); 362 assertThat(table.columnKeySet()).containsExactly(1, 2, 3).inOrder(); 363 assertThat(table.row('c').keySet()).containsExactly(1, 2, 3).inOrder(); 364 } 365 testSerialization_empty()366 public void testSerialization_empty() { 367 validateReserialization(ImmutableTable.of()); 368 } 369 testSerialization_singleElement()370 public void testSerialization_singleElement() { 371 validateReserialization(ImmutableTable.of('a', 2, "foo")); 372 } 373 testDenseSerialization_manualOrder()374 public void testDenseSerialization_manualOrder() { 375 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 376 builder.put('b', 2, "foo"); 377 builder.put('b', 1, "bar"); 378 builder.put('a', 2, "baz"); 379 Table<Character, Integer, String> table = builder.build(); 380 assertThat(table).isInstanceOf(DenseImmutableTable.class); 381 validateReserialization(table); 382 } 383 testDenseSerialization_rowOrder()384 public void testDenseSerialization_rowOrder() { 385 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 386 builder.orderRowsBy(Ordering.<Character>natural()); 387 builder.put('b', 2, "foo"); 388 builder.put('b', 1, "bar"); 389 builder.put('a', 2, "baz"); 390 Table<Character, Integer, String> table = builder.build(); 391 assertThat(table).isInstanceOf(DenseImmutableTable.class); 392 validateReserialization(table); 393 } 394 testDenseSerialization_columnOrder()395 public void testDenseSerialization_columnOrder() { 396 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 397 builder.orderColumnsBy(Ordering.<Integer>natural()); 398 builder.put('b', 2, "foo"); 399 builder.put('b', 1, "bar"); 400 builder.put('a', 2, "baz"); 401 Table<Character, Integer, String> table = builder.build(); 402 assertThat(table).isInstanceOf(DenseImmutableTable.class); 403 validateReserialization(table); 404 } 405 testDenseSerialization_bothOrders()406 public void testDenseSerialization_bothOrders() { 407 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 408 builder.orderRowsBy(Ordering.<Character>natural()); 409 builder.orderColumnsBy(Ordering.<Integer>natural()); 410 builder.put('b', 2, "foo"); 411 builder.put('b', 1, "bar"); 412 builder.put('a', 2, "baz"); 413 Table<Character, Integer, String> table = builder.build(); 414 assertThat(table).isInstanceOf(DenseImmutableTable.class); 415 validateReserialization(table); 416 } 417 testSparseSerialization_manualOrder()418 public void testSparseSerialization_manualOrder() { 419 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 420 builder.put('b', 2, "foo"); 421 builder.put('b', 1, "bar"); 422 builder.put('a', 2, "baz"); 423 builder.put('c', 3, "cat"); 424 builder.put('d', 4, "dog"); 425 Table<Character, Integer, String> table = builder.build(); 426 assertThat(table).isInstanceOf(SparseImmutableTable.class); 427 validateReserialization(table); 428 } 429 testSparseSerialization_rowOrder()430 public void testSparseSerialization_rowOrder() { 431 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 432 builder.orderRowsBy(Ordering.<Character>natural()); 433 builder.put('b', 2, "foo"); 434 builder.put('b', 1, "bar"); 435 builder.put('a', 2, "baz"); 436 builder.put('c', 3, "cat"); 437 builder.put('d', 4, "dog"); 438 Table<Character, Integer, String> table = builder.build(); 439 assertThat(table).isInstanceOf(SparseImmutableTable.class); 440 validateReserialization(table); 441 } 442 testSparseSerialization_columnOrder()443 public void testSparseSerialization_columnOrder() { 444 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 445 builder.orderColumnsBy(Ordering.<Integer>natural()); 446 builder.put('b', 2, "foo"); 447 builder.put('b', 1, "bar"); 448 builder.put('a', 2, "baz"); 449 builder.put('c', 3, "cat"); 450 builder.put('d', 4, "dog"); 451 Table<Character, Integer, String> table = builder.build(); 452 assertThat(table).isInstanceOf(SparseImmutableTable.class); 453 validateReserialization(table); 454 } 455 testSparseSerialization_bothOrders()456 public void testSparseSerialization_bothOrders() { 457 ImmutableTable.Builder<Character, Integer, String> builder = ImmutableTable.builder(); 458 builder.orderRowsBy(Ordering.<Character>natural()); 459 builder.orderColumnsBy(Ordering.<Integer>natural()); 460 builder.put('b', 2, "foo"); 461 builder.put('b', 1, "bar"); 462 builder.put('a', 2, "baz"); 463 builder.put('c', 3, "cat"); 464 builder.put('d', 4, "dog"); 465 Table<Character, Integer, String> table = builder.build(); 466 assertThat(table).isInstanceOf(SparseImmutableTable.class); 467 validateReserialization(table); 468 } 469 validateReserialization(Table<R, C, V> original)470 private static <R, C, V> void validateReserialization(Table<R, C, V> original) { 471 Table<R, C, V> copy = SerializableTester.reserializeAndAssert(original); 472 assertThat(copy.cellSet()).containsExactlyElementsIn(original.cellSet()).inOrder(); 473 assertThat(copy.rowKeySet()).containsExactlyElementsIn(original.rowKeySet()).inOrder(); 474 assertThat(copy.columnKeySet()).containsExactlyElementsIn(original.columnKeySet()).inOrder(); 475 } 476 477 @GwtIncompatible // Mind-bogglingly slow in GWT 478 @AndroidIncompatible // slow testOverflowCondition()479 public void testOverflowCondition() { 480 // See https://code.google.com/p/guava-libraries/issues/detail?id=1322 for details. 481 ImmutableTable.Builder<Integer, Integer, String> builder = ImmutableTable.builder(); 482 for (int i = 1; i < 0x10000; i++) { 483 builder.put(i, 0, "foo"); 484 builder.put(0, i, "bar"); 485 } 486 assertTrue(builder.build() instanceof SparseImmutableTable); 487 } 488 489 @GwtIncompatible // NullPointerTester 490 @Override testNullPointerInstance()491 public void testNullPointerInstance() { 492 if (isAndroid()) { 493 /* 494 * NPT fails under the old versions of Android we test under because it performs reflection on 495 * ImmutableTable, which declares static methods that refer to Collector, which is unavailable 496 * under such versions. 497 * 498 * We use a runtime check here instead of @AndroidIncompatible: @AndroidIncompatible operates 499 * by stripping annotated methods entirely, and if we strip this method, then JUnit would just 500 * run the supermethod as usual. 501 * 502 * TODO: b/292578973: Use @AndroidIncompatible if we change our system to keep the methods in 503 * place but to have the test runner skip them. However, note that if we choose to *both* 504 * strip the methods *and* have the test runner not run them (for some unusual cases in which 505 * we don't run the stripping test for technical reasons), then we'd be back to the problem 506 * described above, since the supermethod is *not* annotated @AndroidIncompatible (since it 507 * works fine with the other Table implementations). 508 */ 509 return; 510 } 511 super.testNullPointerInstance(); 512 } 513 isAndroid()514 private static boolean isAndroid() { 515 return System.getProperty("java.runtime.name", "").contains("Android"); 516 } 517 } 518