1 /* 2 * Copyright 2018 Google LLC 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 package com.google.auto.value; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static org.junit.Assert.fail; 20 21 import com.google.common.testing.EqualsTester; 22 import java.io.ByteArrayInputStream; 23 import java.io.ByteArrayOutputStream; 24 import java.io.ObjectInputStream; 25 import java.io.ObjectOutputStream; 26 import java.io.Serializable; 27 import java.lang.annotation.Retention; 28 import java.lang.annotation.RetentionPolicy; 29 import java.util.concurrent.ExecutionException; 30 import org.junit.Test; 31 import org.junit.runner.RunWith; 32 import org.junit.runners.JUnit4; 33 34 /** @author emcmanus@google.com (Éamonn McManus) */ 35 @RunWith(JUnit4.class) 36 public class AutoOneOfTest { 37 @AutoValue 38 public abstract static class Dog { name()39 public abstract String name(); 40 create(String name)41 public static Dog create(String name) { 42 return new AutoValue_AutoOneOfTest_Dog(name); 43 } 44 bark()45 public void bark() {} 46 } 47 48 @AutoValue 49 public abstract static class Cat { create()50 public static Cat create() { 51 return new AutoValue_AutoOneOfTest_Cat(); 52 } 53 meow()54 public void meow() {} 55 } 56 57 @AutoValue 58 public abstract static class TigerShark { create()59 public static TigerShark create() { 60 return new AutoValue_AutoOneOfTest_TigerShark(); 61 } 62 chomp()63 public void chomp() {} 64 } 65 66 @AutoOneOf(Pet.Kind.class) 67 public abstract static class Pet { 68 create(Dog dog)69 public static Pet create(Dog dog) { 70 return AutoOneOf_AutoOneOfTest_Pet.dog(dog); 71 } 72 create(Cat cat)73 public static Pet create(Cat cat) { 74 return AutoOneOf_AutoOneOfTest_Pet.cat(cat); 75 } 76 create(TigerShark shark)77 public static Pet create(TigerShark shark) { 78 return AutoOneOf_AutoOneOfTest_Pet.tigerShark(shark); 79 } 80 dog()81 public abstract Dog dog(); 82 cat()83 public abstract Cat cat(); 84 tigerShark()85 public abstract TigerShark tigerShark(); 86 87 public enum Kind { 88 DOG, 89 CAT, 90 TIGER_SHARK 91 } 92 getKind()93 public abstract Kind getKind(); 94 } 95 96 @Test equality()97 public void equality() { 98 Dog marvin1 = Dog.create("Marvin"); 99 Pet petMarvin1 = Pet.create(marvin1); 100 Dog marvin2 = Dog.create("Marvin"); 101 Pet petMarvin2 = Pet.create(marvin2); 102 Dog isis = Dog.create("Isis"); 103 Pet petIsis = Pet.create(isis); 104 Cat cat = Cat.create(); 105 new EqualsTester() 106 .addEqualityGroup(marvin1, marvin2) 107 .addEqualityGroup(petMarvin1, petMarvin2) 108 .addEqualityGroup(petIsis) 109 .addEqualityGroup(cat) 110 .addEqualityGroup("foo") 111 .testEquals(); 112 } 113 114 @Test getCorrectType()115 public void getCorrectType() { 116 Dog marvin = Dog.create("Marvin"); 117 Pet petMarvin = Pet.create(marvin); 118 assertThat(petMarvin.dog()).isSameInstanceAs(marvin); 119 } 120 121 @Test getWrongType()122 public void getWrongType() { 123 Cat cat = Cat.create(); 124 Pet petCat = Pet.create(cat); 125 try { 126 petCat.tigerShark(); 127 fail(); 128 } catch (UnsupportedOperationException e) { 129 assertThat(e).hasMessageThat().containsMatch("(?i:cat)"); 130 } 131 } 132 133 @Test string()134 public void string() { 135 Dog marvin = Dog.create("Marvin"); 136 Pet petMarvin = Pet.create(marvin); 137 assertThat(petMarvin.toString()).isEqualTo("Pet{dog=Dog{name=Marvin}}"); 138 } 139 140 @Test getKind()141 public void getKind() { 142 Dog marvin = Dog.create("Marvin"); 143 Pet petMarvin = Pet.create(marvin); 144 Cat cat = Cat.create(); 145 Pet petCat = Pet.create(cat); 146 TigerShark shark = TigerShark.create(); 147 Pet petShark = Pet.create(shark); 148 assertThat(petMarvin.getKind()).isEqualTo(Pet.Kind.DOG); 149 assertThat(petCat.getKind()).isEqualTo(Pet.Kind.CAT); 150 assertThat(petShark.getKind()).isEqualTo(Pet.Kind.TIGER_SHARK); 151 } 152 153 @Test cannotBeNull()154 public void cannotBeNull() { 155 try { 156 Pet.create((Dog) null); 157 fail(); 158 } catch (NullPointerException expected) { 159 } 160 } 161 162 // Package-private case. 163 164 @AutoOneOf(IntegerOrString.Kind.class) 165 abstract static class IntegerOrString { 166 enum Kind { 167 INTEGER, 168 STRING 169 } 170 getKind()171 abstract Kind getKind(); 172 integer()173 abstract int integer(); 174 string()175 abstract String string(); 176 of(int x)177 static IntegerOrString of(int x) { 178 return AutoOneOf_AutoOneOfTest_IntegerOrString.integer(x); 179 } 180 of(String x)181 static IntegerOrString of(String x) { 182 return AutoOneOf_AutoOneOfTest_IntegerOrString.string(x); 183 } 184 } 185 186 @Test packagePrivate()187 public void packagePrivate() { 188 IntegerOrString integer = IntegerOrString.of(23); 189 IntegerOrString string = IntegerOrString.of("23"); 190 assertThat(integer.getKind()).isEqualTo(IntegerOrString.Kind.INTEGER); 191 assertThat(string.getKind()).isEqualTo(IntegerOrString.Kind.STRING); 192 assertThat(integer.integer()).isEqualTo(23); 193 assertThat(string.string()).isEqualTo("23"); 194 assertThat(integer).isNotEqualTo(string); 195 try { 196 integer.string(); 197 fail(); 198 } catch (UnsupportedOperationException e) { 199 assertThat(e).hasMessageThat().containsMatch("(?i:integer)"); 200 } 201 } 202 203 @AutoOneOf(Pet.Kind.class) 204 public abstract static class PetWithGet { getDog()205 public abstract Dog getDog(); 206 getCat()207 public abstract Cat getCat(); 208 getTigerShark()209 public abstract TigerShark getTigerShark(); 210 create(Dog dog)211 public static PetWithGet create(Dog dog) { 212 return AutoOneOf_AutoOneOfTest_PetWithGet.dog(dog); 213 } 214 create(Cat cat)215 public static PetWithGet create(Cat cat) { 216 return AutoOneOf_AutoOneOfTest_PetWithGet.cat(cat); 217 } 218 create(TigerShark shark)219 public static PetWithGet create(TigerShark shark) { 220 return AutoOneOf_AutoOneOfTest_PetWithGet.tigerShark(shark); 221 } 222 getKind()223 public abstract Pet.Kind getKind(); 224 } 225 226 @Test getPrefix()227 public void getPrefix() { 228 Dog marvin = Dog.create("Marvin"); 229 PetWithGet petMarvin = PetWithGet.create(marvin); 230 assertThat(petMarvin.toString()).isEqualTo("PetWithGet{dog=Dog{name=Marvin}}"); 231 } 232 233 @AutoOneOf(Primitive.Kind.class) 234 public abstract static class Primitive { 235 public enum Kind { 236 A_BYTE, 237 A_SHORT, 238 AN_INT, 239 A_LONG, 240 A_FLOAT, 241 A_DOUBLE, 242 A_CHAR, 243 A_BOOLEAN 244 } 245 getKind()246 public abstract Kind getKind(); 247 aByte()248 public abstract byte aByte(); 249 aShort()250 public abstract short aShort(); 251 anInt()252 public abstract int anInt(); 253 aLong()254 public abstract long aLong(); 255 aFloat()256 public abstract float aFloat(); 257 aDouble()258 public abstract double aDouble(); 259 aChar()260 public abstract char aChar(); 261 aBoolean()262 public abstract boolean aBoolean(); 263 of(byte x)264 public static Primitive of(byte x) { 265 return AutoOneOf_AutoOneOfTest_Primitive.aByte(x); 266 } 267 of(short x)268 public static Primitive of(short x) { 269 return AutoOneOf_AutoOneOfTest_Primitive.aShort(x); 270 } 271 of(int x)272 public static Primitive of(int x) { 273 return AutoOneOf_AutoOneOfTest_Primitive.anInt(x); 274 } 275 of(long x)276 public static Primitive of(long x) { 277 return AutoOneOf_AutoOneOfTest_Primitive.aLong(x); 278 } 279 of(float x)280 public static Primitive of(float x) { 281 return AutoOneOf_AutoOneOfTest_Primitive.aFloat(x); 282 } 283 of(double x)284 public static Primitive of(double x) { 285 return AutoOneOf_AutoOneOfTest_Primitive.aDouble(x); 286 } 287 of(char x)288 public static Primitive of(char x) { 289 return AutoOneOf_AutoOneOfTest_Primitive.aChar(x); 290 } 291 of(boolean x)292 public static Primitive of(boolean x) { 293 return AutoOneOf_AutoOneOfTest_Primitive.aBoolean(x); 294 } 295 } 296 297 @Test primitive()298 public void primitive() { 299 Primitive primitive = Primitive.of(17); 300 assertThat(primitive.anInt()).isEqualTo(17); 301 assertThat(primitive.toString()).isEqualTo("Primitive{anInt=17}"); 302 } 303 304 @AutoOneOf(OneOfOne.Kind.class) 305 public abstract static class OneOfOne { 306 public enum Kind { 307 DOG 308 } 309 getDog()310 public abstract Dog getDog(); 311 create(Dog dog)312 public static OneOfOne create(Dog dog) { 313 return AutoOneOf_AutoOneOfTest_OneOfOne.dog(dog); 314 } 315 getKind()316 public abstract Kind getKind(); 317 } 318 319 @Test oneOfOne()320 public void oneOfOne() { 321 Dog marvin = Dog.create("Marvin"); 322 OneOfOne oneOfMarvin = OneOfOne.create(marvin); 323 assertThat(oneOfMarvin.toString()).isEqualTo("OneOfOne{dog=Dog{name=Marvin}}"); 324 assertThat(oneOfMarvin.getKind()).isEqualTo(OneOfOne.Kind.DOG); 325 } 326 327 // We allow this for consistency, even though it's obviously pretty useless. 328 // The generated code might be rubbish, but it compiles. No concrete implementation is generated 329 // so there isn't really anything to test beyond that it compiles. 330 @AutoOneOf(OneOfNone.Kind.class) 331 public abstract static class OneOfNone { 332 public enum Kind {} 333 getKind()334 public abstract Kind getKind(); 335 } 336 337 // Testing generics. Typically generics will be a bit messy because the @AutoOneOf class must 338 // have type parameters for every property that needs them, even though any given property 339 // might not use all the type parameters. 340 @AutoOneOf(TaskResult.Kind.class) 341 public abstract static class TaskResult<V extends Serializable> { 342 public enum Kind { 343 VALUE, 344 EXCEPTION 345 } 346 getKind()347 public abstract Kind getKind(); 348 value()349 public abstract V value(); 350 exception()351 public abstract Throwable exception(); 352 get()353 public V get() throws ExecutionException { 354 switch (getKind()) { 355 case VALUE: 356 return value(); 357 case EXCEPTION: 358 throw new ExecutionException(exception()); 359 } 360 throw new AssertionError(getKind()); 361 } 362 value(V value)363 static <V extends Serializable> TaskResult<V> value(V value) { 364 return AutoOneOf_AutoOneOfTest_TaskResult.value(value); 365 } 366 exception(Throwable exception)367 static TaskResult<?> exception(Throwable exception) { 368 return AutoOneOf_AutoOneOfTest_TaskResult.exception(exception); 369 } 370 } 371 372 @Test taskResultValue()373 public void taskResultValue() throws Exception { 374 TaskResult<String> result = TaskResult.value("foo"); 375 assertThat(result.get()).isEqualTo("foo"); 376 } 377 378 @Test taskResultException()379 public void taskResultException() { 380 Exception exception = new IllegalArgumentException("oops"); 381 TaskResult<?> result = TaskResult.exception(exception); 382 try { 383 result.get(); 384 fail(); 385 } catch (ExecutionException e) { 386 assertThat(e).hasCauseThat().isEqualTo(exception); 387 } 388 } 389 390 @AutoOneOf(CustomToString.Kind.class) 391 public abstract static class CustomToString { 392 public enum Kind { 393 ACE 394 } 395 getKind()396 public abstract Kind getKind(); 397 ace()398 public abstract String ace(); 399 ace(String ace)400 public static CustomToString ace(String ace) { 401 return AutoOneOf_AutoOneOfTest_CustomToString.ace(ace); 402 } 403 404 @Override toString()405 public String toString() { 406 return "blim"; 407 } 408 } 409 410 // If you have an explicit toString() method, we won't override it. 411 @Test customToString()412 public void customToString() { 413 CustomToString x = CustomToString.ace("ceg"); 414 assertThat(x.toString()).isEqualTo("blim"); 415 } 416 417 @AutoOneOf(AbstractToString.Kind.class) 418 public abstract static class AbstractToString { 419 public enum Kind { 420 ACE 421 } 422 getKind()423 public abstract Kind getKind(); 424 ace()425 public abstract String ace(); 426 ace(String ace)427 public static AbstractToString ace(String ace) { 428 return AutoOneOf_AutoOneOfTest_AbstractToString.ace(ace); 429 } 430 431 @Override toString()432 public abstract String toString(); 433 } 434 435 // If you have an explicit abstract toString() method, we will implement it. 436 @Test abstractToString()437 public void abstractToString() { 438 AbstractToString x = AbstractToString.ace("ceg"); 439 assertThat(x.toString()).isEqualTo("AbstractToString{ace=ceg}"); 440 } 441 442 // "package" is a reserved word. You probably don't want to have a property with that name, 443 // but if you insist, you can get one by using getFoo()-style methods. We leak our renaming 444 // scheme here (package0) and for users that that bothers they can just avoid having properties 445 // that are reserved words. 446 @AutoOneOf(LetterOrPackage.Kind.class) 447 public abstract static class LetterOrPackage { 448 public enum Kind { 449 LETTER, 450 PACKAGE 451 } 452 getKind()453 public abstract Kind getKind(); 454 getLetter()455 public abstract String getLetter(); 456 getPackage()457 public abstract String getPackage(); 458 ofLetter(String letter)459 public static LetterOrPackage ofLetter(String letter) { 460 return AutoOneOf_AutoOneOfTest_LetterOrPackage.letter(letter); 461 } 462 ofPackage(String pkg)463 public static LetterOrPackage ofPackage(String pkg) { 464 return AutoOneOf_AutoOneOfTest_LetterOrPackage.package0(pkg); 465 } 466 } 467 468 @Test reservedWordProperty()469 public void reservedWordProperty() { 470 LetterOrPackage pkg = LetterOrPackage.ofPackage("pacquet"); 471 assertThat(pkg.toString()).isEqualTo("LetterOrPackage{package=pacquet}"); 472 } 473 474 @AutoOneOf(ArrayValue.Kind.class) 475 public abstract static class ArrayValue { 476 public enum Kind { 477 STRING, 478 INTS 479 } 480 getKind()481 public abstract Kind getKind(); 482 string()483 public abstract String string(); 484 485 @SuppressWarnings("mutable") ints()486 public abstract int[] ints(); 487 ofString(String string)488 public static ArrayValue ofString(String string) { 489 return AutoOneOf_AutoOneOfTest_ArrayValue.string(string); 490 } 491 ofInts(int[] ints)492 public static ArrayValue ofInts(int[] ints) { 493 return AutoOneOf_AutoOneOfTest_ArrayValue.ints(ints); 494 } 495 } 496 497 @Test arrayValues()498 public void arrayValues() { 499 ArrayValue string = ArrayValue.ofString("foo"); 500 ArrayValue ints1 = ArrayValue.ofInts(new int[] {17, 23}); 501 ArrayValue ints2 = ArrayValue.ofInts(new int[] {17, 23}); 502 new EqualsTester().addEqualityGroup(string).addEqualityGroup(ints1, ints2).testEquals(); 503 } 504 505 @Retention(RetentionPolicy.RUNTIME) 506 public @interface CopyTest { value()507 int value(); 508 } 509 510 @AutoOneOf(AnnotationNotCopied.Kind.class) 511 @CopyTest(23) 512 public abstract static class AnnotationNotCopied { 513 public enum Kind { 514 ACE 515 } 516 getKind()517 public abstract Kind getKind(); 518 ace()519 public abstract String ace(); 520 ace(String ace)521 public static AnnotationNotCopied ace(String ace) { 522 return AutoOneOf_AutoOneOfTest_AnnotationNotCopied.ace(ace); 523 } 524 } 525 526 @Test classAnnotationsNotCopiedByDefault()527 public void classAnnotationsNotCopiedByDefault() { 528 assertThat(AnnotationNotCopied.class.isAnnotationPresent(CopyTest.class)).isTrue(); 529 AnnotationNotCopied ace = AnnotationNotCopied.ace("ace"); 530 assertThat(ace.getClass().isAnnotationPresent(CopyTest.class)).isFalse(); 531 } 532 533 @AutoOneOf(AnnotationCopied.Kind.class) 534 @CopyTest(23) 535 @AutoValue.CopyAnnotations 536 public abstract static class AnnotationCopied { 537 public enum Kind { 538 ACE 539 } 540 getKind()541 public abstract Kind getKind(); 542 ace()543 public abstract String ace(); 544 ace(String ace)545 public static AnnotationCopied ace(String ace) { 546 return AutoOneOf_AutoOneOfTest_AnnotationCopied.ace(ace); 547 } 548 } 549 550 @Test classAnnotationsCopiedIfCopyAnnotations()551 public void classAnnotationsCopiedIfCopyAnnotations() { 552 assertThat(AnnotationCopied.class.isAnnotationPresent(CopyTest.class)).isTrue(); 553 AnnotationCopied ace = AnnotationCopied.ace("ace"); 554 assertThat(ace.getClass().isAnnotationPresent(CopyTest.class)).isTrue(); 555 assertThat(ace.getClass().getAnnotation(CopyTest.class).value()).isEqualTo(23); 556 } 557 558 @AutoOneOf(MaybeEmpty.Kind.class) 559 public abstract static class MaybeEmpty implements Serializable { 560 private static final long serialVersionUID = 1L; 561 562 public enum Kind { 563 EMPTY, 564 STRING, 565 } 566 getKind()567 public abstract Kind getKind(); 568 empty()569 public abstract void empty(); 570 string()571 public abstract String string(); 572 ofEmpty()573 public static MaybeEmpty ofEmpty() { 574 return AutoOneOf_AutoOneOfTest_MaybeEmpty.empty(); 575 } 576 ofString(String s)577 public static MaybeEmpty ofString(String s) { 578 return AutoOneOf_AutoOneOfTest_MaybeEmpty.string(s); 579 } 580 } 581 582 @Test voidPropertyIsSingleton()583 public void voidPropertyIsSingleton() { 584 MaybeEmpty empty1 = MaybeEmpty.ofEmpty(); 585 MaybeEmpty empty2 = MaybeEmpty.ofEmpty(); 586 assertThat(empty1).isSameInstanceAs(empty2); 587 } 588 589 @Test voidPropertyRemainsSingletonWhenDeserialized()590 public void voidPropertyRemainsSingletonWhenDeserialized() throws Exception { 591 MaybeEmpty empty1 = MaybeEmpty.ofEmpty(); 592 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 593 // We're still compiling this with -source 6, so we can't use try-with-resources. 594 ObjectOutputStream dos = new ObjectOutputStream(baos); 595 dos.writeObject(empty1); 596 dos.close(); 597 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 598 ObjectInputStream ois = new ObjectInputStream(bais); 599 MaybeEmpty empty2 = (MaybeEmpty) ois.readObject(); 600 assertThat(empty2).isSameInstanceAs(empty1); 601 } 602 603 @Test voidPropertyToString()604 public void voidPropertyToString() { 605 MaybeEmpty empty = MaybeEmpty.ofEmpty(); 606 assertThat(empty.toString()).isEqualTo("MaybeEmpty{empty}"); 607 } 608 609 @Test voidPropertyHashCodeIsIdentity()610 public void voidPropertyHashCodeIsIdentity() { 611 MaybeEmpty empty = MaybeEmpty.ofEmpty(); 612 assertThat(empty.hashCode()).isEqualTo(System.identityHashCode(empty)); 613 } 614 615 @Test voidPropertyGetterDoesNothing()616 public void voidPropertyGetterDoesNothing() { 617 MaybeEmpty empty = MaybeEmpty.ofEmpty(); 618 empty.empty(); 619 } 620 621 @Test voidPropertyNotEqualToNonVoid()622 public void voidPropertyNotEqualToNonVoid() { 623 MaybeEmpty empty = MaybeEmpty.ofEmpty(); 624 MaybeEmpty notEmpty = MaybeEmpty.ofString("foo"); 625 assertThat(empty).isNotEqualTo(notEmpty); 626 assertThat(notEmpty).isNotEqualTo(empty); 627 } 628 629 @Test voidPropertyWrongType()630 public void voidPropertyWrongType() { 631 MaybeEmpty notEmpty = MaybeEmpty.ofString("foo"); 632 try { 633 notEmpty.empty(); 634 fail(); 635 } catch (UnsupportedOperationException e) { 636 assertThat(e).hasMessageThat().containsMatch("(?i:string)"); 637 } 638 } 639 640 @AutoOneOf(OneOfArray.Kind.class) 641 public abstract static class OneOfArray { 642 public enum Kind { 643 INTS 644 } 645 getKind()646 public abstract Kind getKind(); 647 648 @SuppressWarnings("mutable") ints()649 public abstract int[] ints(); 650 ofInts(int[] s)651 public static OneOfArray ofInts(int[] s) { 652 return AutoOneOf_AutoOneOfTest_OneOfArray.ints(s); 653 } 654 } 655 656 @Test arrayToString()657 public void arrayToString() { 658 OneOfArray oneOfArray = OneOfArray.ofInts(new int[] {1, 2}); 659 assertThat(oneOfArray.toString()).isEqualTo("OneOfArray{ints=[1, 2]}"); 660 } 661 } 662