1 /* 2 * Copyright (C) 2020 The Dagger 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 dagger.internal.codegen; 18 19 import static com.google.testing.compile.CompilationSubject.assertThat; 20 import static com.google.testing.compile.Compiler.javac; 21 import static java.util.stream.Collectors.joining; 22 23 import com.google.common.collect.ImmutableList; 24 import com.google.common.collect.ImmutableSet; 25 import com.google.testing.compile.Compilation; 26 import com.google.testing.compile.Compiler; 27 import com.google.testing.compile.JavaFileObjects; 28 import java.util.Arrays; 29 import javax.tools.JavaFileObject; 30 import org.junit.Test; 31 import org.junit.runner.RunWith; 32 import org.junit.runners.JUnit4; 33 34 @RunWith(JUnit4.class) 35 public class ComponentShardTest { 36 private static final int BINDINGS_PER_SHARD = 10; 37 38 @Test testNewShardCreated()39 public void testNewShardCreated() { 40 // Create 2N + 1 bindings: N in DaggerTestComponent, N in Shard1, and 1 in Shard2 41 int numBindings = 2 * BINDINGS_PER_SHARD + 1; 42 ImmutableList.Builder<JavaFileObject> javaFileObjects = ImmutableList.builder(); 43 ImmutableList.Builder<String> entryPoints = ImmutableList.builder(); 44 for (int i = 0; i < numBindings; i++) { 45 String bindingName = "Binding" + i; 46 entryPoints.add(String.format("%1$s get%1$s();", bindingName)); 47 entryPoints.add(String.format("Provider<%1$s> get%1$sProvider();", bindingName)); 48 49 // Add dependencies between main component and shard1: 9 -> 10 -> Provider<9> 50 // Add dependencies between shard1 and shard2: 19 -> 20 -> Provider<19> 51 switch (i) { 52 case 9: 53 javaFileObjects.add(createBinding(bindingName, "Binding10 dep")); 54 break; 55 case 10: 56 javaFileObjects.add(createBinding(bindingName, "Provider<Binding9> dep")); 57 break; 58 case 19: 59 javaFileObjects.add(createBinding(bindingName, "Binding20 dep")); 60 break; 61 case 20: 62 javaFileObjects.add(createBinding(bindingName, "Provider<Binding19> dep")); 63 break; 64 default: 65 javaFileObjects.add(createBinding(bindingName)); 66 break; 67 } 68 } 69 70 javaFileObjects.add(createComponent(entryPoints.build())); 71 72 // This generated component shows a couple things: 73 // 1. Binding locations: 74 // * Binding #9 belongs to DaggerTestComponent 75 // * Binding #10 belongs to Shard1 76 // * Binding #20 belongs to Shard2 77 // 2. DaggerTestComponent entry point methods: 78 // * Binding #9 implementation is inlined DaggerTestComponent. 79 // * Binding #10 implementation is delegated to Shard1. 80 // * Binding #20 implementation is delegated to Shard2. 81 // 3. Dependencies between component and shard: 82 // * Binding #9 in DaggerTestComponent depends on #10 in Shard1. 83 // * Binding #10 in Shard1 depends on Provider<#9> in DaggerTestComponent. 84 // 4. Dependencies between shard and shard: 85 // * Binding #19 in Shard1 depends on #20 in Shard2. 86 // * Binding #20 in Shard2 depends on Provider<#19> in Shard1. 87 JavaFileObject generatedComponent = 88 JavaFileObjects.forSourceLines( 89 "dagger.internal.codegen.DaggerTestComponent", 90 "package dagger.internal.codegen;", 91 GeneratedLines.generatedAnnotations(), 92 "final class DaggerTestComponent implements TestComponent {", 93 " private final Shard1 shard1 = new Shard1();", 94 "", 95 " private volatile Provider<Binding9> binding9Provider;", 96 "", 97 " private volatile Object binding9 = new MemoizedSentinel();", 98 "", 99 " @Override", 100 " public Binding9 getBinding9() {", 101 " Object local = binding9;", 102 " if (local instanceof MemoizedSentinel) {", 103 " synchronized (local) {", 104 " local = binding9;", 105 " if (local instanceof MemoizedSentinel) {", 106 " local = new Binding9(DaggerTestComponent.this.shard1.binding10());", 107 " binding9 = DoubleCheck.reentrantCheck(binding9, local);", 108 " }", 109 " }", 110 " }", 111 " return (Binding9) local;", 112 " }", 113 "", 114 " @Override", 115 " public Provider<Binding9> getBinding9Provider() {", 116 " Object local = binding9Provider;", 117 " if (local == null) {", 118 " local = new SwitchingProvider<>(9);", 119 " binding9Provider = (Provider<Binding9>) local;", 120 " }", 121 " return (Provider<Binding9>) local;", 122 " }", 123 "", 124 " @Override", 125 " public Binding10 getBinding10() {", 126 " return DaggerTestComponent.this.shard1.binding10();", 127 " }", 128 "", 129 " @Override", 130 " public Provider<Binding10> getBinding10Provider() {", 131 " return DaggerTestComponent.this.shard1.binding10Provider();", 132 " }", 133 "", 134 " @Override", 135 " public Binding20 getBinding20() {", 136 " return DaggerTestComponent.this.shard2.binding20();", 137 " }", 138 "", 139 " @Override", 140 " public Provider<Binding20> getBinding20Provider() {", 141 " return DaggerTestComponent.this.shard2.binding20Provider();", 142 " }", 143 "", 144 " private final class Shard1 {", 145 " private volatile Object binding10 = new MemoizedSentinel();", 146 "", 147 " private volatile Provider<Binding10> binding10Provider;", 148 "", 149 " private volatile Provider<Binding19> binding19Provider;", 150 "", 151 " private volatile Object binding19 = new MemoizedSentinel();", 152 "", 153 " private Binding10 binding10() {", 154 " Object local = binding10;", 155 " if (local instanceof MemoizedSentinel) {", 156 " synchronized (local) {", 157 " local = binding10;", 158 " if (local instanceof MemoizedSentinel) {", 159 " local = new Binding10(", 160 " DaggerTestComponent.this.getBinding9Provider());", 161 " binding10 = DoubleCheck.reentrantCheck(binding10, local);", 162 " }", 163 " }", 164 " }", 165 " return (Binding10) local;", 166 " }", 167 "", 168 " private Provider<Binding10> binding10Provider() {", 169 " Object local = binding10Provider;", 170 " if (local == null) {", 171 " local = new SwitchingProvider<>(10);", 172 " binding10Provider = (Provider<Binding10>) local;", 173 " }", 174 " return (Provider<Binding10>) local;", 175 " }", 176 "", 177 " private Provider<Binding19> binding19Provider() {", 178 " Object local = binding19Provider;", 179 " if (local == null) {", 180 " local = new SwitchingProvider<>(19);", 181 " binding19Provider = (Provider<Binding19>) local;", 182 " }", 183 " return (Provider<Binding19>) local;", 184 " }", 185 "", 186 " private Binding19 binding19() {", 187 " Object local = binding19;", 188 " if (local instanceof MemoizedSentinel) {", 189 " synchronized (local) {", 190 " local = binding19;", 191 " if (local instanceof MemoizedSentinel) {", 192 " local = new Binding19(DaggerTestComponent.this.shard2.binding20());", 193 " binding19 = DoubleCheck.reentrantCheck(binding19, local);", 194 " }", 195 " }", 196 " }", 197 " return (Binding19) local;", 198 " }", 199 " }", 200 "", 201 " private final class Shard2 {", 202 " private volatile Object binding20 = new MemoizedSentinel();", 203 "", 204 " private volatile Provider<Binding20> binding20Provider;", 205 "", 206 " private Binding20 binding20() {", 207 " Object local = binding20;", 208 " if (local instanceof MemoizedSentinel) {", 209 " synchronized (local) {", 210 " local = binding20;", 211 " if (local instanceof MemoizedSentinel) {", 212 " local = new Binding20(", 213 " DaggerTestComponent.this.shard1.binding19Provider());", 214 " binding20 = DoubleCheck.reentrantCheck(binding20, local);", 215 " }", 216 " }", 217 " }", 218 " return (Binding20) local;", 219 " }", 220 "", 221 " private Provider<Binding20> binding20Provider() {", 222 " Object local = binding20Provider;", 223 " if (local == null) {", 224 " local = new SwitchingProvider<>(20);", 225 " binding20Provider = (Provider<Binding20>) local;", 226 " }", 227 " return (Provider<Binding20>) local;", 228 " }", 229 " }", 230 "}"); 231 232 Compilation compilation = compilerWithAndroidMode().compile(javaFileObjects.build()); 233 assertThat(compilation).succeededWithoutWarnings(); 234 assertThat(compilation) 235 .generatedSourceFile("dagger.internal.codegen.DaggerTestComponent") 236 .containsElementsIn(generatedComponent); 237 } 238 createBinding(String bindingName, String... deps)239 private static JavaFileObject createBinding(String bindingName, String... deps) { 240 return JavaFileObjects.forSourceLines( 241 "dagger.internal.codegen." + bindingName, 242 "package dagger.internal.codegen;", 243 "", 244 "import javax.inject.Inject;", 245 "import javax.inject.Provider;", 246 "import javax.inject.Singleton;", 247 "", 248 "@Singleton", 249 "final class " + bindingName + " {", 250 " @Inject", 251 " " + bindingName + "(" + Arrays.stream(deps).collect(joining(", ")) + ") {}", 252 "}"); 253 } 254 createComponent(ImmutableList<String> entryPoints)255 private static JavaFileObject createComponent(ImmutableList<String> entryPoints) { 256 return JavaFileObjects.forSourceLines( 257 "dagger.internal.codegen.TestComponent", 258 "package dagger.internal.codegen;", 259 "", 260 "import dagger.Component;", 261 "import javax.inject.Provider;", 262 "import javax.inject.Singleton;", 263 "", 264 "@Singleton", 265 "@Component", 266 "interface TestComponent {", 267 " " + entryPoints.stream().collect(joining("\n ")), 268 "}"); 269 } 270 compilerWithAndroidMode()271 private static Compiler compilerWithAndroidMode() { 272 return javac() 273 .withProcessors(new ComponentProcessor()) 274 .withOptions( 275 ImmutableSet.builder() 276 .add("-Adagger.keysPerComponentShard=" + BINDINGS_PER_SHARD) 277 .addAll(CompilerMode.FAST_INIT_MODE.javacopts()) 278 .build()); 279 } 280 } 281