Line data Source code
1 : // Copyright (C) 2013 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.acceptance;
16 :
17 : import static com.google.common.base.Preconditions.checkArgument;
18 : import static com.google.common.collect.ImmutableList.toImmutableList;
19 : import static com.google.common.truth.OptionalSubject.optionals;
20 : import static com.google.common.truth.Truth.assertThat;
21 : import static com.google.common.truth.Truth.assertWithMessage;
22 : import static com.google.common.truth.Truth8.assertThat;
23 : import static com.google.common.truth.TruthJUnit.assume;
24 : import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
25 : import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
26 : import static com.google.gerrit.entities.Patch.COMMIT_MSG;
27 : import static com.google.gerrit.entities.Patch.MERGE_LIST;
28 : import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
29 : import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.TOPIC_CLOSURE;
30 : import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
31 : import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
32 : import static com.google.gerrit.server.project.ProjectCache.illegalState;
33 : import static com.google.gerrit.server.project.testing.TestLabels.label;
34 : import static com.google.gerrit.server.project.testing.TestLabels.value;
35 : import static com.google.gerrit.testing.GerritJUnit.assertThrows;
36 : import static java.nio.charset.StandardCharsets.UTF_8;
37 : import static java.util.Objects.requireNonNull;
38 : import static java.util.stream.Collectors.toList;
39 : import static org.eclipse.jgit.lib.Constants.HEAD;
40 :
41 : import com.github.rholder.retry.BlockStrategy;
42 : import com.google.common.base.Strings;
43 : import com.google.common.base.Ticker;
44 : import com.google.common.collect.ImmutableList;
45 : import com.google.common.collect.ImmutableMap;
46 : import com.google.common.collect.ImmutableSet;
47 : import com.google.common.collect.Iterables;
48 : import com.google.common.collect.Lists;
49 : import com.google.common.primitives.Chars;
50 : import com.google.common.testing.FakeTicker;
51 : import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
52 : import com.google.gerrit.acceptance.PushOneCommit.Result;
53 : import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
54 : import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
55 : import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
56 : import com.google.gerrit.acceptance.testsuite.request.SshSessionFactory;
57 : import com.google.gerrit.common.Nullable;
58 : import com.google.gerrit.entities.AccessSection;
59 : import com.google.gerrit.entities.Account;
60 : import com.google.gerrit.entities.AccountGroup;
61 : import com.google.gerrit.entities.Address;
62 : import com.google.gerrit.entities.BooleanProjectConfig;
63 : import com.google.gerrit.entities.BranchNameKey;
64 : import com.google.gerrit.entities.Change;
65 : import com.google.gerrit.entities.EmailHeader;
66 : import com.google.gerrit.entities.EmailHeader.StringEmailHeader;
67 : import com.google.gerrit.entities.GroupDescription;
68 : import com.google.gerrit.entities.GroupReference;
69 : import com.google.gerrit.entities.InternalGroup;
70 : import com.google.gerrit.entities.LabelFunction;
71 : import com.google.gerrit.entities.LabelType;
72 : import com.google.gerrit.entities.LabelValue;
73 : import com.google.gerrit.entities.PatchSet;
74 : import com.google.gerrit.entities.Permission;
75 : import com.google.gerrit.entities.PermissionRule;
76 : import com.google.gerrit.entities.PermissionRule.Action;
77 : import com.google.gerrit.entities.Project;
78 : import com.google.gerrit.entities.RefNames;
79 : import com.google.gerrit.entities.SubmitRequirement;
80 : import com.google.gerrit.extensions.api.GerritApi;
81 : import com.google.gerrit.extensions.api.changes.ChangeApi;
82 : import com.google.gerrit.extensions.api.changes.ReviewInput;
83 : import com.google.gerrit.extensions.api.changes.RevisionApi;
84 : import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
85 : import com.google.gerrit.extensions.api.changes.SubmittedTogetherOption;
86 : import com.google.gerrit.extensions.api.projects.BranchApi;
87 : import com.google.gerrit.extensions.api.projects.BranchInfo;
88 : import com.google.gerrit.extensions.api.projects.BranchInput;
89 : import com.google.gerrit.extensions.api.projects.ProjectInput;
90 : import com.google.gerrit.extensions.client.InheritableBoolean;
91 : import com.google.gerrit.extensions.client.ListChangesOption;
92 : import com.google.gerrit.extensions.client.ProjectWatchInfo;
93 : import com.google.gerrit.extensions.client.ReviewerState;
94 : import com.google.gerrit.extensions.client.SubmitType;
95 : import com.google.gerrit.extensions.common.AccountInfo;
96 : import com.google.gerrit.extensions.common.ChangeInfo;
97 : import com.google.gerrit.extensions.common.ChangeType;
98 : import com.google.gerrit.extensions.common.CommentInfo;
99 : import com.google.gerrit.extensions.common.DiffInfo;
100 : import com.google.gerrit.extensions.common.EditInfo;
101 : import com.google.gerrit.extensions.restapi.BinaryResult;
102 : import com.google.gerrit.extensions.restapi.IdString;
103 : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
104 : import com.google.gerrit.extensions.restapi.RestApiException;
105 : import com.google.gerrit.json.OutputFormat;
106 : import com.google.gerrit.server.GerritPersonIdent;
107 : import com.google.gerrit.server.IdentifiedUser;
108 : import com.google.gerrit.server.PatchSetUtil;
109 : import com.google.gerrit.server.PluginUser;
110 : import com.google.gerrit.server.account.AccountCache;
111 : import com.google.gerrit.server.account.AccountState;
112 : import com.google.gerrit.server.account.Accounts;
113 : import com.google.gerrit.server.account.GroupBackend;
114 : import com.google.gerrit.server.account.GroupCache;
115 : import com.google.gerrit.server.account.externalids.ExternalId;
116 : import com.google.gerrit.server.change.BatchAbandon;
117 : import com.google.gerrit.server.change.ChangeFinder;
118 : import com.google.gerrit.server.change.ChangeResource;
119 : import com.google.gerrit.server.change.FileContentUtil;
120 : import com.google.gerrit.server.change.RevisionResource;
121 : import com.google.gerrit.server.config.AllProjectsName;
122 : import com.google.gerrit.server.config.AllUsersName;
123 : import com.google.gerrit.server.config.CanonicalWebUrl;
124 : import com.google.gerrit.server.config.GerritInstanceId;
125 : import com.google.gerrit.server.config.GerritServerConfig;
126 : import com.google.gerrit.server.config.PluginConfigFactory;
127 : import com.google.gerrit.server.config.SitePaths;
128 : import com.google.gerrit.server.git.GitRepositoryManager;
129 : import com.google.gerrit.server.git.meta.MetaDataUpdate;
130 : import com.google.gerrit.server.group.SystemGroupBackend;
131 : import com.google.gerrit.server.index.account.AccountIndexer;
132 : import com.google.gerrit.server.index.change.ChangeIndexer;
133 : import com.google.gerrit.server.notedb.AbstractChangeNotes;
134 : import com.google.gerrit.server.notedb.ChangeNoteUtil;
135 : import com.google.gerrit.server.notedb.ChangeNotes;
136 : import com.google.gerrit.server.notedb.ChangeNotesCommit;
137 : import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
138 : import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
139 : import com.google.gerrit.server.plugins.TestServerPlugin;
140 : import com.google.gerrit.server.project.ProjectCache;
141 : import com.google.gerrit.server.project.ProjectConfig;
142 : import com.google.gerrit.server.query.change.ChangeData;
143 : import com.google.gerrit.server.query.change.InternalChangeQuery;
144 : import com.google.gerrit.server.restapi.change.Revisions;
145 : import com.google.gerrit.server.update.BatchUpdate;
146 : import com.google.gerrit.server.util.git.DelegateSystemReader;
147 : import com.google.gerrit.testing.ConfigSuite;
148 : import com.google.gerrit.testing.FakeEmailSender;
149 : import com.google.gerrit.testing.FakeEmailSender.Message;
150 : import com.google.gerrit.testing.SshMode;
151 : import com.google.gerrit.testing.TestTimeUtil;
152 : import com.google.gson.Gson;
153 : import com.google.inject.Inject;
154 : import com.google.inject.Module;
155 : import com.google.inject.Provider;
156 : import java.io.ByteArrayOutputStream;
157 : import java.io.File;
158 : import java.io.IOException;
159 : import java.lang.reflect.Modifier;
160 : import java.sql.Timestamp;
161 : import java.time.Instant;
162 : import java.util.ArrayList;
163 : import java.util.Arrays;
164 : import java.util.Collection;
165 : import java.util.Collections;
166 : import java.util.Comparator;
167 : import java.util.EnumSet;
168 : import java.util.HashMap;
169 : import java.util.List;
170 : import java.util.Map;
171 : import java.util.Objects;
172 : import java.util.Optional;
173 : import java.util.UUID;
174 : import java.util.regex.Pattern;
175 : import java.util.stream.Collectors;
176 : import java.util.stream.IntStream;
177 : import org.eclipse.jgit.api.Git;
178 : import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
179 : import org.eclipse.jgit.junit.TestRepository;
180 : import org.eclipse.jgit.lib.Config;
181 : import org.eclipse.jgit.lib.Constants;
182 : import org.eclipse.jgit.lib.ObjectId;
183 : import org.eclipse.jgit.lib.PersonIdent;
184 : import org.eclipse.jgit.lib.Ref;
185 : import org.eclipse.jgit.lib.Repository;
186 : import org.eclipse.jgit.revwalk.RevCommit;
187 : import org.eclipse.jgit.revwalk.RevSort;
188 : import org.eclipse.jgit.revwalk.RevTree;
189 : import org.eclipse.jgit.revwalk.RevWalk;
190 : import org.eclipse.jgit.storage.file.FileBasedConfig;
191 : import org.eclipse.jgit.transport.Transport;
192 : import org.eclipse.jgit.util.FS;
193 : import org.eclipse.jgit.util.SystemReader;
194 : import org.junit.After;
195 : import org.junit.Before;
196 : import org.junit.ClassRule;
197 : import org.junit.Rule;
198 : import org.junit.rules.TemporaryFolder;
199 : import org.junit.rules.TestRule;
200 : import org.junit.runner.Description;
201 : import org.junit.runner.RunWith;
202 : import org.junit.runners.model.Statement;
203 :
204 : @RunWith(ConfigSuite.class)
205 132 : public abstract class AbstractDaemonTest {
206 :
207 : /**
208 : * Test methods without special annotations will use a common server for efficiency reasons. The
209 : * server is torn down after the test class is done.
210 : */
211 : private static GerritServer commonServer;
212 :
213 : private static Description firstTest;
214 :
215 132 : @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder();
216 :
217 : @ConfigSuite.Parameter public Config baseConfig;
218 : @ConfigSuite.Name private String configName;
219 :
220 132 : @Rule
221 : public TestRule testRunner =
222 132 : new TestRule() {
223 : @Override
224 : public Statement apply(Statement base, Description description) {
225 132 : return new Statement() {
226 : @Override
227 : public void evaluate() throws Throwable {
228 132 : if (firstTest == null) {
229 132 : firstTest = description;
230 : }
231 132 : beforeTest(description);
232 132 : ProjectResetter.Config input = requireNonNull(resetProjects());
233 :
234 : try (ProjectResetter resetter =
235 132 : projectResetter != null ? projectResetter.builder().build(input) : null) {
236 132 : AbstractDaemonTest.this.resetter = resetter;
237 132 : base.evaluate();
238 : } finally {
239 132 : AbstractDaemonTest.this.resetter = null;
240 132 : afterTest();
241 : }
242 132 : }
243 : };
244 : }
245 : };
246 :
247 : @Inject @CanonicalWebUrl protected Provider<String> canonicalWebUrl;
248 : @Inject @GerritPersonIdent protected Provider<PersonIdent> serverIdent;
249 : @Inject @GerritServerConfig protected Config cfg;
250 : @Inject @GerritInstanceId @Nullable protected String instanceId;
251 : @Inject protected AcceptanceTestRequestScope atrScope;
252 : @Inject protected AccountCache accountCache;
253 : @Inject protected AccountCreator accountCreator;
254 : @Inject protected Accounts accounts;
255 : @Inject protected AllProjectsName allProjects;
256 : @Inject protected AllUsersName allUsers;
257 : @Inject protected BatchUpdate.Factory batchUpdateFactory;
258 : @Inject protected ChangeData.Factory changeDataFactory;
259 : @Inject protected ChangeFinder changeFinder;
260 : @Inject protected ChangeIndexer indexer;
261 : @Inject protected ChangeNoteUtil changeNoteUtil;
262 : @Inject protected ChangeResource.Factory changeResourceFactory;
263 : @Inject protected FakeEmailSender sender;
264 : @Inject protected GerritApi gApi;
265 : @Inject protected GitRepositoryManager repoManager;
266 : @Inject protected GroupBackend groupBackend;
267 : @Inject protected GroupCache groupCache;
268 : @Inject protected IdentifiedUser.GenericFactory identifiedUserFactory;
269 : @Inject protected MetaDataUpdate.Server metaDataUpdateFactory;
270 : @Inject protected PatchSetUtil psUtil;
271 : @Inject protected ProjectCache projectCache;
272 : @Inject protected ProjectConfig.Factory projectConfigFactory;
273 : @Inject protected ProjectResetter.Builder.Factory projectResetter;
274 : @Inject protected Provider<InternalChangeQuery> queryProvider;
275 : @Inject protected PushOneCommit.Factory pushFactory;
276 : @Inject protected PluginConfigFactory pluginConfig;
277 : @Inject protected Revisions revisions;
278 : @Inject protected SystemGroupBackend systemGroupBackend;
279 : @Inject protected ChangeNotes.Factory notesFactory;
280 : @Inject protected BatchAbandon batchAbandon;
281 : @Inject protected TestSshKeys sshKeys;
282 : @Inject protected TestTicker testTicker;
283 :
284 : protected EventRecorder eventRecorder;
285 : protected GerritServer server;
286 : protected Project.NameKey project;
287 : protected RestSession adminRestSession;
288 : protected RestSession userRestSession;
289 : protected RestSession anonymousRestSession;
290 : protected SshSession adminSshSession;
291 : protected SshSession userSshSession;
292 : protected TestAccount admin;
293 : protected TestAccount user;
294 : protected TestRepository<InMemoryRepository> testRepo;
295 : protected String resourcePrefix;
296 : protected Description description;
297 : protected GerritServer.Description testMethodDescription;
298 :
299 : protected boolean testRequiresSsh;
300 132 : protected BlockStrategy noSleepBlockStrategy = t -> {}; // Don't sleep in tests.
301 :
302 : @Inject private AbstractChangeNotes.Args changeNotesArgs;
303 : @Inject private AccountIndexer accountIndexer;
304 : @Inject private EventRecorder.Factory eventRecorderFactory;
305 : @Inject private InProcessProtocol inProcessProtocol;
306 : @Inject private PluginGuiceEnvironment pluginGuiceEnvironment;
307 : @Inject private PluginUser.Factory pluginUserFactory;
308 : @Inject private RequestScopeOperations requestScopeOperations;
309 : @Inject private SitePaths sitePaths;
310 : @Inject private ProjectOperations projectOperations;
311 :
312 : private ProjectResetter resetter;
313 : private List<Repository> toClose;
314 : private String systemTimeZone;
315 : private SystemReader oldSystemReader;
316 :
317 : @Before
318 : public void clearSender() {
319 132 : if (sender != null) {
320 132 : sender.clear();
321 : }
322 132 : }
323 :
324 : @Before
325 : public void startEventRecorder() {
326 132 : if (eventRecorderFactory != null) {
327 132 : eventRecorder = eventRecorderFactory.create(admin);
328 : }
329 132 : }
330 :
331 : @Before
332 : public void assumeSshIfRequired() {
333 132 : if (testRequiresSsh) {
334 : // If the test uses ssh, we use assume() to make sure ssh is enabled on
335 : // the test suite. JUnit will skip tests annotated with @UseSsh if we
336 : // disable them using the command line flag.
337 13 : assume().that(SshMode.useSsh()).isTrue();
338 : }
339 132 : }
340 :
341 : @After
342 : public void verifyNoPiiInChangeNotes() throws RestApiException, IOException {
343 132 : if (testMethodDescription.verifyNoPiiInChangeNotes()) {
344 4 : verifyNoAccountDetailsInChangeNotes();
345 : }
346 132 : }
347 :
348 : @After
349 : public void closeEventRecorder() {
350 132 : if (eventRecorder != null) {
351 132 : eventRecorder.close();
352 : }
353 132 : }
354 :
355 : @ConfigSuite.AfterConfig
356 : public static void stopCommonServer() throws Exception {
357 132 : if (commonServer != null) {
358 : try {
359 128 : commonServer.close();
360 0 : } catch (Exception e) {
361 0 : throw new AssertionError(
362 : "Error stopping common server in "
363 0 : + (firstTest != null ? firstTest.getTestClass().getName() : "unknown test class"),
364 : e);
365 : } finally {
366 128 : commonServer = null;
367 : }
368 : }
369 132 : }
370 :
371 : /** Controls which project and branches should be reset after each test case. */
372 : protected ProjectResetter.Config resetProjects() {
373 131 : return new ProjectResetter.Config()
374 : // Don't reset all refs so that refs/sequences/changes is not touched and change IDs are
375 : // not reused.
376 131 : .reset(allProjects, RefNames.REFS_CONFIG)
377 : // Don't reset refs/sequences/accounts so that account IDs are not reused.
378 131 : .reset(
379 : allUsers,
380 : RefNames.REFS_CONFIG,
381 : RefNames.REFS_USERS + "*",
382 : RefNames.REFS_EXTERNAL_IDS,
383 : RefNames.REFS_GROUPNAMES,
384 : RefNames.REFS_GROUPS + "*",
385 : RefNames.REFS_STARRED_CHANGES + "*",
386 : RefNames.REFS_DRAFT_COMMENTS + "*");
387 : }
388 :
389 : protected void restartAsSlave() throws Exception {
390 3 : closeSsh();
391 3 : server = GerritServer.restartAsSlave(server);
392 3 : server.getTestInjector().injectMembers(this);
393 3 : if (resetter != null) {
394 3 : server.getTestInjector().injectMembers(resetter);
395 : }
396 3 : initSsh();
397 3 : }
398 :
399 : protected void restart() throws Exception {
400 1 : server = GerritServer.restart(server, createModule(), createSshModule());
401 1 : server.getTestInjector().injectMembers(this);
402 1 : if (resetter != null) {
403 1 : server.getTestInjector().injectMembers(resetter);
404 : }
405 1 : initSsh();
406 1 : }
407 :
408 : protected void reindexAccount(Account.Id accountId) {
409 132 : accountIndexer.index(accountId);
410 132 : }
411 :
412 : protected static Config submitWholeTopicEnabledConfig() {
413 11 : Config cfg = new Config();
414 11 : cfg.setBoolean("change", null, "submitWholeTopic", true);
415 11 : return cfg;
416 : }
417 :
418 : protected boolean isSubmitWholeTopicEnabled() {
419 10 : return cfg.getBoolean("change", null, "submitWholeTopic", false);
420 : }
421 :
422 : protected boolean isContributorAgreementsEnabled() {
423 1 : return cfg.getBoolean("auth", null, "contributorAgreements", false);
424 : }
425 :
426 : protected void beforeTest(Description description) throws Exception {
427 : // SystemReader must be overridden before creating any repos, since they read the user/system
428 : // configs at initialization time, and are then stored in the RepositoryCache forever.
429 132 : oldSystemReader = setFakeSystemReader(temporaryFolder.getRoot());
430 :
431 132 : this.description = description;
432 132 : GerritServer.Description classDesc =
433 132 : GerritServer.Description.forTestClass(description, configName);
434 132 : GerritServer.Description methodDesc =
435 132 : GerritServer.Description.forTestMethod(description, configName);
436 132 : testMethodDescription = methodDesc;
437 :
438 132 : testRequiresSsh = classDesc.useSshAnnotation() || methodDesc.useSshAnnotation();
439 132 : if (!testRequiresSsh) {
440 125 : baseConfig.setString("sshd", null, "listenAddress", "off");
441 : }
442 :
443 132 : baseConfig.unset("gerrit", null, "canonicalWebUrl");
444 132 : baseConfig.unset("httpd", null, "listenUrl");
445 :
446 132 : baseConfig.setInt("index", null, "batchThreads", -1);
447 :
448 132 : Module module = createModule();
449 132 : Module auditModule = createAuditModule();
450 132 : Module sshModule = createSshModule();
451 132 : if (classDesc.equals(methodDesc) && !classDesc.sandboxed() && !methodDesc.sandboxed()) {
452 128 : if (commonServer == null) {
453 128 : commonServer =
454 128 : GerritServer.initAndStart(
455 : temporaryFolder, classDesc, baseConfig, module, auditModule, sshModule);
456 : }
457 128 : server = commonServer;
458 : } else {
459 53 : server =
460 53 : GerritServer.initAndStart(
461 : temporaryFolder, methodDesc, baseConfig, module, auditModule, sshModule);
462 : }
463 :
464 132 : server.getTestInjector().injectMembers(this);
465 132 : Transport.register(inProcessProtocol);
466 132 : toClose = Collections.synchronizedList(new ArrayList<>());
467 :
468 132 : admin = accountCreator.admin();
469 132 : user = accountCreator.user1();
470 :
471 : // Evict and reindex accounts in case tests modify them.
472 132 : reindexAccount(admin.id());
473 132 : reindexAccount(user.id());
474 :
475 132 : adminRestSession = new RestSession(server, admin);
476 132 : userRestSession = new RestSession(server, user);
477 132 : anonymousRestSession = new RestSession(server, null);
478 :
479 132 : initSsh();
480 :
481 132 : resourcePrefix =
482 : UNSAFE_PROJECT_NAME
483 132 : .matcher(description.getClassName() + "_" + description.getMethodName() + "_")
484 132 : .replaceAll("");
485 :
486 132 : Context ctx = newRequestContext(admin);
487 132 : atrScope.set(ctx);
488 132 : ProjectInput in = projectInput(description);
489 132 : gApi.projects().create(in);
490 132 : project = Project.nameKey(in.name);
491 132 : if (!classDesc.skipProjectClone()) {
492 129 : testRepo = cloneProject(project, getCloneAsAccount(description));
493 : }
494 :
495 : // Set the clock step last, so that the test setup isn't consuming any timestamps after the
496 : // clock has been set.
497 132 : setTimeSettings(classDesc.useSystemTime(), classDesc.useClockStep(), classDesc.useTimezone());
498 132 : setTimeSettings(
499 132 : methodDesc.useSystemTime(), methodDesc.useClockStep(), methodDesc.useTimezone());
500 132 : }
501 :
502 : private static SystemReader setFakeSystemReader(File tempDir) {
503 132 : SystemReader oldSystemReader = SystemReader.getInstance();
504 132 : SystemReader.setInstance(
505 132 : new DelegateSystemReader(oldSystemReader) {
506 : @Override
507 : public FileBasedConfig openJGitConfig(Config parent, FS fs) {
508 131 : return new FileBasedConfig(parent, new File(tempDir, "jgit.config"), FS.detect());
509 : }
510 :
511 : @Override
512 : public FileBasedConfig openUserConfig(Config parent, FS fs) {
513 131 : return new FileBasedConfig(parent, new File(tempDir, "user.config"), FS.detect());
514 : }
515 :
516 : @Override
517 : public FileBasedConfig openSystemConfig(Config parent, FS fs) {
518 131 : return new FileBasedConfig(parent, new File(tempDir, "system.config"), FS.detect());
519 : }
520 : });
521 132 : return oldSystemReader;
522 : }
523 :
524 : private void setTimeSettings(
525 : boolean useSystemTime,
526 : @Nullable UseClockStep useClockStep,
527 : @Nullable UseTimezone useTimezone) {
528 132 : if (useSystemTime) {
529 2 : TestTimeUtil.useSystemTime();
530 132 : } else if (useClockStep != null) {
531 22 : TestTimeUtil.resetWithClockStep(useClockStep.clockStep(), useClockStep.clockStepUnit());
532 22 : if (useClockStep.startAtEpoch()) {
533 1 : TestTimeUtil.setClock(Timestamp.from(Instant.EPOCH));
534 : }
535 : }
536 132 : if (useTimezone != null) {
537 13 : systemTimeZone = System.setProperty("user.timezone", useTimezone.timezone());
538 : }
539 132 : }
540 :
541 : private void resetTimeSettings() {
542 132 : TestTimeUtil.useSystemTime();
543 132 : if (systemTimeZone != null) {
544 13 : System.setProperty("user.timezone", systemTimeZone);
545 13 : systemTimeZone = null;
546 : }
547 132 : }
548 :
549 : /** Override to bind an additional Guice module */
550 : public Module createModule() {
551 125 : return null;
552 : }
553 :
554 : /** Override to bind an alternative audit Guice module */
555 : public Module createAuditModule() {
556 132 : return null;
557 : }
558 :
559 : /** Override to bind an additional Guice module for SSH injector */
560 : public Module createSshModule() {
561 132 : return null;
562 : }
563 :
564 : protected void initSsh() throws Exception {
565 132 : if (testRequiresSsh
566 13 : && SshMode.useSsh()
567 : && (adminSshSession == null || userSshSession == null)) {
568 : // Create Ssh sessions
569 13 : SshSessionFactory.initSsh();
570 13 : Context ctx = newRequestContext(user);
571 13 : atrScope.set(ctx);
572 13 : userSshSession = ctx.getSession();
573 13 : userSshSession.open();
574 13 : ctx = newRequestContext(admin);
575 13 : atrScope.set(ctx);
576 13 : adminSshSession = ctx.getSession();
577 13 : adminSshSession.open();
578 : }
579 132 : }
580 :
581 : private TestAccount getCloneAsAccount(Description description) {
582 129 : TestProjectInput ann = description.getAnnotation(TestProjectInput.class);
583 129 : return accountCreator.get(ann != null ? ann.cloneAs() : "admin");
584 : }
585 :
586 : /** Generate default project properties based on test description */
587 : private ProjectInput projectInput(Description description) {
588 132 : ProjectInput in = new ProjectInput();
589 132 : TestProjectInput ann = description.getAnnotation(TestProjectInput.class);
590 132 : in.name = name("project");
591 132 : in.branches = ImmutableList.of(Constants.R_HEADS + Constants.MASTER);
592 132 : if (ann != null) {
593 18 : in.parent = Strings.emptyToNull(ann.parent());
594 18 : in.description = Strings.emptyToNull(ann.description());
595 18 : in.createEmptyCommit = ann.createEmptyCommit();
596 18 : in.submitType = ann.submitType();
597 18 : in.useContentMerge = ann.useContributorAgreements();
598 18 : in.useSignedOffBy = ann.useSignedOffBy();
599 18 : in.useContentMerge = ann.useContentMerge();
600 18 : in.rejectEmptyCommit = ann.rejectEmptyCommit();
601 18 : in.enableSignedPush = ann.enableSignedPush();
602 18 : in.requireSignedPush = ann.requireSignedPush();
603 : } else {
604 : // Defaults should match TestProjectConfig, omitting nullable values.
605 132 : in.createEmptyCommit = true;
606 : }
607 132 : updateProjectInput(in);
608 132 : return in;
609 : }
610 :
611 : /**
612 : * Modify a project input before creating the initial test project.
613 : *
614 : * @param in input; may be modified in place.
615 : */
616 : protected void updateProjectInput(ProjectInput in) {
617 : // Default implementation does nothing.
618 126 : }
619 :
620 132 : private static final Pattern UNSAFE_PROJECT_NAME = Pattern.compile("[^a-zA-Z0-9._/-]+");
621 :
622 : protected Git git() {
623 5 : return testRepo.git();
624 : }
625 :
626 : protected InMemoryRepository repo() {
627 23 : return testRepo.getRepository();
628 : }
629 :
630 : /**
631 : * Return a resource name scoped to this test method.
632 : *
633 : * <p>Test methods in a single class by default share a running server. For any resource name you
634 : * require to be unique to a test method, wrap it in a call to this method.
635 : *
636 : * @param name resource name (group, project, topic, etc.)
637 : * @return name prefixed by a string unique to this test method.
638 : */
639 : protected String name(String name) {
640 132 : return resourcePrefix + name;
641 : }
642 :
643 : protected Project.NameKey createProjectOverAPI(
644 : String nameSuffix,
645 : @Nullable Project.NameKey parent,
646 : boolean createEmptyCommit,
647 : @Nullable SubmitType submitType)
648 : throws RestApiException {
649 4 : ProjectInput in = new ProjectInput();
650 4 : in.name = name(nameSuffix);
651 4 : in.parent = parent != null ? parent.get() : null;
652 4 : in.submitType = submitType;
653 4 : in.createEmptyCommit = createEmptyCommit;
654 4 : gApi.projects().create(in);
655 4 : return Project.nameKey(in.name);
656 : }
657 :
658 : protected TestRepository<InMemoryRepository> cloneProject(Project.NameKey p) throws Exception {
659 22 : return cloneProject(p, admin);
660 : }
661 :
662 : protected TestRepository<InMemoryRepository> cloneProject(
663 : Project.NameKey p, TestAccount testAccount) throws Exception {
664 132 : return GitUtil.cloneProject(p, registerRepoConnection(p, testAccount));
665 : }
666 :
667 : /**
668 : * Register a repository connection over the test protocol.
669 : *
670 : * @return a URI string that can be used to connect to this repository for both fetch and push.
671 : */
672 : protected String registerRepoConnection(Project.NameKey p, TestAccount testAccount)
673 : throws Exception {
674 132 : InProcessProtocol.Context ctx =
675 132 : new InProcessProtocol.Context(identifiedUserFactory, testAccount.id(), p);
676 132 : Repository repo = repoManager.openRepository(p);
677 132 : toClose.add(repo);
678 132 : return inProcessProtocol.register(ctx, repo).toString();
679 : }
680 :
681 : protected void afterTest() throws Exception {
682 132 : Transport.unregister(inProcessProtocol);
683 132 : for (Repository repo : toClose) {
684 132 : repo.close();
685 132 : }
686 132 : closeSsh();
687 132 : resetTimeSettings();
688 132 : if (server != commonServer) {
689 53 : server.close();
690 53 : server = null;
691 : }
692 132 : SystemReader.setInstance(oldSystemReader);
693 132 : oldSystemReader = null;
694 : // Set useDefaultTicker in afterTest, so the next beforeTest will use the default ticker
695 132 : testTicker.useDefaultTicker();
696 132 : }
697 :
698 : protected void closeSsh() {
699 132 : if (adminSshSession != null) {
700 13 : adminSshSession.close();
701 13 : adminSshSession = null;
702 : }
703 132 : if (userSshSession != null) {
704 13 : userSshSession.close();
705 13 : userSshSession = null;
706 : }
707 132 : }
708 :
709 : /**
710 : * Verify that NoteDB commits do not persist user-sensitive information, by running checks for all
711 : * commits in {@link RefNames#changeMetaRef} for all changes, created during the test.
712 : *
713 : * <p>These tests prevent regression, assuming appropriate test coverage for new features. The
714 : * verification is disabled by default and can be enabled using {@link VerifyNoPiiInChangeNotes}
715 : * annotation either on test class or method.
716 : */
717 : protected void verifyNoAccountDetailsInChangeNotes() throws RestApiException, IOException {
718 4 : List<ChangeInfo> allChanges = gApi.changes().query().get();
719 :
720 4 : List<AccountState> allAccounts = accounts.all();
721 4 : for (ChangeInfo change : allChanges) {
722 4 : try (Repository repo = repoManager.openRepository(Project.nameKey(change.project))) {
723 4 : String metaRefName =
724 4 : RefNames.changeMetaRef(Change.Id.tryParse(change._number.toString()).get());
725 4 : ObjectId metaTip = repo.getRefDatabase().exactRef(metaRefName).getObjectId();
726 4 : ChangeNotesRevWalk revWalk = ChangeNotesCommit.newRevWalk(repo);
727 4 : revWalk.reset();
728 4 : revWalk.markStart(revWalk.parseCommit(metaTip));
729 : ChangeNotesCommit commit;
730 4 : while ((commit = revWalk.next()) != null) {
731 4 : String fullMessage = commit.getFullMessage();
732 4 : for (AccountState accountState : allAccounts) {
733 4 : Account account = accountState.account();
734 4 : assertThat(fullMessage).doesNotContain(account.getName());
735 4 : if (account.fullName() != null) {
736 4 : assertThat(fullMessage).doesNotContain(account.fullName());
737 : }
738 4 : if (account.displayName() != null) {
739 4 : assertThat(fullMessage).doesNotContain(account.displayName());
740 : }
741 4 : if (account.preferredEmail() != null) {
742 4 : assertThat(fullMessage).doesNotContain(account.preferredEmail());
743 : }
744 4 : if (accountState.userName().isPresent()) {
745 4 : assertThat(fullMessage).doesNotContain(accountState.userName().get());
746 : }
747 4 : List<String> allEmails =
748 4 : accountState.externalIds().stream()
749 4 : .map(ExternalId::email)
750 4 : .filter(Objects::nonNull)
751 4 : .collect(toImmutableList());
752 4 : for (String email : allEmails) {
753 4 : assertThat(fullMessage).doesNotContain(email);
754 4 : }
755 4 : }
756 4 : }
757 : }
758 4 : }
759 4 : }
760 :
761 : protected TestRepository<?>.CommitBuilder commitBuilder() throws Exception {
762 16 : return testRepo.branch("HEAD").commit().insertChangeId();
763 : }
764 :
765 : protected TestRepository<?>.CommitBuilder amendBuilder() throws Exception {
766 4 : ObjectId head = repo().exactRef("HEAD").getObjectId();
767 4 : TestRepository<?>.CommitBuilder b = testRepo.amendRef("HEAD");
768 4 : Optional<String> id = GitUtil.getChangeId(testRepo, head);
769 : // TestRepository behaves like "git commit --amend -m foo", which does not
770 : // preserve an existing Change-Id. Tests probably want this.
771 4 : if (id.isPresent()) {
772 4 : b.insertChangeId(id.get().substring(1));
773 : } else {
774 0 : b.insertChangeId();
775 : }
776 4 : return b;
777 : }
778 :
779 : protected PushOneCommit.Result createChange() throws Exception {
780 61 : return createChange("refs/for/master");
781 : }
782 :
783 : protected PushOneCommit.Result createChange(String ref) throws Exception {
784 61 : PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
785 61 : PushOneCommit.Result result = push.to(ref);
786 61 : result.assertOkStatus();
787 61 : return result;
788 : }
789 :
790 : protected PushOneCommit.Result createChange(TestRepository<InMemoryRepository> repo)
791 : throws Exception {
792 3 : PushOneCommit push = pushFactory.create(admin.newIdent(), repo);
793 3 : PushOneCommit.Result result = push.to("refs/for/master");
794 3 : result.assertOkStatus();
795 3 : return result;
796 : }
797 :
798 : protected PushOneCommit.Result createMergeCommitChange(String ref) throws Exception {
799 5 : return createMergeCommitChange(ref, "foo");
800 : }
801 :
802 : protected PushOneCommit.Result createMergeCommitChange(String ref, String file) throws Exception {
803 6 : ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
804 :
805 6 : PushOneCommit.Result p1 =
806 : pushFactory
807 6 : .create(
808 6 : admin.newIdent(),
809 : testRepo,
810 : "parent 1",
811 6 : ImmutableMap.of(file, "foo-1", "bar", "bar-1"))
812 6 : .to(ref);
813 :
814 : // reset HEAD in order to create a sibling of the first change
815 6 : testRepo.reset(initial);
816 :
817 6 : PushOneCommit.Result p2 =
818 : pushFactory
819 6 : .create(
820 6 : admin.newIdent(),
821 : testRepo,
822 : "parent 2",
823 6 : ImmutableMap.of(file, "foo-2", "bar", "bar-2"))
824 6 : .to(ref);
825 :
826 6 : PushOneCommit m =
827 6 : pushFactory.create(
828 6 : admin.newIdent(), testRepo, "merge", ImmutableMap.of(file, "foo-1", "bar", "bar-2"));
829 6 : m.setParents(ImmutableList.of(p1.getCommit(), p2.getCommit()));
830 6 : PushOneCommit.Result result = m.to(ref);
831 6 : result.assertOkStatus();
832 6 : return result;
833 : }
834 :
835 : protected PushOneCommit.Result createNParentsMergeCommitChange(String ref, List<String> fileNames)
836 : throws Exception {
837 : // This method creates n different commits and creates a merge commit pointing to all n parents.
838 : // Each commit will contain all the fileNames. Commit i will have the following file names and
839 : // their contents:
840 : // {$file_1_name, ${file_1_name}-1}
841 : // {$file_2_name, ${file_2_name}-1}, etc...
842 : // The merge commit will have:
843 : // {$file_1_name, ${file_1_name}-1}
844 : // {$file_2_name, ${file_2_name}-2},
845 : // {$file_3_name, ${file_3_name}-3}, etc...
846 : // i.e. taking the ith file from the ith commit.
847 2 : int n = fileNames.size();
848 2 : ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
849 :
850 2 : List<PushOneCommit.Result> pushResults = new ArrayList<>();
851 :
852 2 : for (int i = 1; i <= n; i++) {
853 2 : int finalI = i;
854 2 : pushResults.add(
855 : pushFactory
856 2 : .create(
857 2 : admin.newIdent(),
858 : testRepo,
859 : "parent " + i,
860 2 : fileNames.stream().collect(Collectors.toMap(f -> f, f -> f + "-" + finalI)))
861 2 : .to(ref));
862 :
863 : // reset HEAD in order to create a sibling of the first change
864 2 : if (i < n) {
865 2 : testRepo.reset(initial);
866 : }
867 : }
868 :
869 2 : PushOneCommit m =
870 2 : pushFactory.create(
871 2 : admin.newIdent(),
872 : testRepo,
873 : "merge",
874 2 : IntStream.range(1, n + 1)
875 2 : .boxed()
876 2 : .collect(
877 2 : Collectors.toMap(
878 2 : i -> fileNames.get(i - 1), i -> fileNames.get(i - 1) + "-" + i)));
879 :
880 2 : m.setParents(pushResults.stream().map(PushOneCommit.Result::getCommit).collect(toList()));
881 2 : PushOneCommit.Result result = m.to(ref);
882 2 : result.assertOkStatus();
883 2 : return result;
884 : }
885 :
886 : protected PushOneCommit.Result createCommitAndPush(
887 : TestRepository<InMemoryRepository> repo,
888 : String ref,
889 : String commitMsg,
890 : String fileName,
891 : String content)
892 : throws Exception {
893 2 : PushOneCommit.Result result =
894 2 : pushFactory.create(admin.newIdent(), repo, commitMsg, fileName, content).to(ref);
895 2 : result.assertOkStatus();
896 2 : return result;
897 : }
898 :
899 : protected PushOneCommit.Result createChangeWithTopic(
900 : TestRepository<InMemoryRepository> repo,
901 : String topic,
902 : String commitMsg,
903 : String fileName,
904 : String content)
905 : throws Exception {
906 1 : assertThat(topic).isNotEmpty();
907 1 : return createCommitAndPush(
908 1 : repo, "refs/for/master%topic=" + name(topic), commitMsg, fileName, content);
909 : }
910 :
911 : protected PushOneCommit.Result createChange(String subject, String fileName, String content)
912 : throws Exception {
913 23 : PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo, subject, fileName, content);
914 23 : return push.to("refs/for/master");
915 : }
916 :
917 : protected PushOneCommit.Result createChange(
918 : TestRepository<?> repo,
919 : String branch,
920 : String subject,
921 : String fileName,
922 : String content,
923 : String topic)
924 : throws Exception {
925 16 : PushOneCommit push = pushFactory.create(admin.newIdent(), repo, subject, fileName, content);
926 16 : return push.to("refs/for/" + branch + "%topic=" + name(topic));
927 : }
928 :
929 : protected BranchApi createBranch(BranchNameKey branch) throws Exception {
930 13 : return gApi.projects()
931 13 : .name(branch.project().get())
932 13 : .branch(branch.branch())
933 13 : .create(new BranchInput());
934 : }
935 :
936 : protected BranchApi createBranchWithRevision(BranchNameKey branch, String revision)
937 : throws Exception {
938 6 : BranchInput in = new BranchInput();
939 6 : in.revision = revision;
940 6 : return gApi.projects().name(branch.project().get()).branch(branch.branch()).create(in);
941 : }
942 :
943 132 : private static final List<Character> RANDOM =
944 132 : Chars.asList('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
945 :
946 : protected PushOneCommit.Result amendChangeWithUploader(
947 : PushOneCommit.Result change, Project.NameKey projectName, TestAccount account)
948 : throws Exception {
949 4 : TestRepository<InMemoryRepository> repo = cloneProject(projectName, account);
950 4 : GitUtil.fetch(repo, "refs/*:refs/*");
951 4 : repo.reset(change.getCommit());
952 4 : PushOneCommit.Result result =
953 4 : amendChange(
954 4 : change.getChangeId(),
955 : "refs/for/master",
956 : account,
957 : repo,
958 : "new subject",
959 : "new file",
960 4 : "new content " + UUID.randomUUID());
961 4 : return result;
962 : }
963 :
964 : protected PushOneCommit.Result amendChange(String changeId) throws Exception {
965 28 : return amendChange(changeId, "refs/for/master", admin, testRepo);
966 : }
967 :
968 : protected PushOneCommit.Result amendChange(
969 : String changeId, String ref, TestAccount testAccount, TestRepository<?> repo)
970 : throws Exception {
971 31 : Collections.shuffle(RANDOM);
972 31 : return amendChange(
973 : changeId,
974 : ref,
975 : testAccount,
976 : repo,
977 : PushOneCommit.SUBJECT,
978 : PushOneCommit.FILE_NAME,
979 31 : new String(Chars.toArray(RANDOM)));
980 : }
981 :
982 : protected PushOneCommit.Result amendChange(
983 : String changeId, String subject, String fileName, String content) throws Exception {
984 7 : return amendChange(changeId, "refs/for/master", admin, testRepo, subject, fileName, content);
985 : }
986 :
987 : protected PushOneCommit.Result amendChange(
988 : String changeId,
989 : String ref,
990 : TestAccount testAccount,
991 : TestRepository<?> repo,
992 : String subject,
993 : String fileName,
994 : String content)
995 : throws Exception {
996 33 : PushOneCommit push =
997 33 : pushFactory.create(testAccount.newIdent(), repo, subject, fileName, content, changeId);
998 33 : return push.to(ref);
999 : }
1000 :
1001 : protected void merge(PushOneCommit.Result r) throws Exception {
1002 7 : revision(r).review(ReviewInput.approve());
1003 7 : revision(r).submit();
1004 7 : }
1005 :
1006 : protected ChangeInfo info(String id) throws RestApiException {
1007 17 : return gApi.changes().id(id).info();
1008 : }
1009 :
1010 : protected ChangeApi change(Result r) throws RestApiException {
1011 15 : return gApi.changes().id(r.getChange().getId().get());
1012 : }
1013 :
1014 : protected Optional<EditInfo> getEdit(String id) throws RestApiException {
1015 5 : return gApi.changes().id(id).edit().get();
1016 : }
1017 :
1018 : protected ChangeInfo get(String id, ListChangesOption... options) throws RestApiException {
1019 18 : return gApi.changes().id(id).get(options);
1020 : }
1021 :
1022 : protected AccountInfo getAccountInfo(Account.Id accountId) throws RestApiException {
1023 2 : return gApi.accounts().id(accountId.get()).get();
1024 : }
1025 :
1026 : protected List<ChangeInfo> query(String q) throws RestApiException {
1027 9 : return gApi.changes().query(q).get();
1028 : }
1029 :
1030 : private Context newRequestContext(TestAccount account) {
1031 132 : requestScopeOperations.setApiUser(account.id());
1032 132 : return atrScope.get();
1033 : }
1034 :
1035 : protected Account getAccount(Account.Id accountId) {
1036 5 : return getAccountState(accountId).account();
1037 : }
1038 :
1039 : protected AccountState getAccountState(Account.Id accountId) {
1040 5 : Optional<AccountState> accountState = accountCache.get(accountId);
1041 5 : assertWithMessage("account %s", accountId.get())
1042 5 : .about(optionals())
1043 5 : .that(accountState)
1044 5 : .isPresent();
1045 5 : return accountState.get();
1046 : }
1047 :
1048 : protected AutoCloseable disableNoteDb() {
1049 6 : changeNotesArgs.failOnLoadForTest.set(true);
1050 6 : Context oldContext = atrScope.disableNoteDb();
1051 6 : return () -> {
1052 6 : changeNotesArgs.failOnLoadForTest.set(false);
1053 6 : atrScope.set(oldContext);
1054 6 : };
1055 : }
1056 :
1057 : protected static Gson newGson() {
1058 15 : return OutputFormat.JSON_COMPACT.newGson();
1059 : }
1060 :
1061 : protected RevisionApi revision(PushOneCommit.Result r) throws Exception {
1062 18 : return gApi.changes().id(r.getChangeId()).current();
1063 : }
1064 :
1065 : protected void setUseSignedOffBy(InheritableBoolean value) throws Exception {
1066 3 : try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
1067 3 : ProjectConfig config = projectConfigFactory.read(md);
1068 3 : config.updateProject(p -> p.setBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY, value));
1069 3 : config.commit(md);
1070 3 : projectCache.evictAndReindex(config.getProject());
1071 : }
1072 3 : }
1073 :
1074 : protected void setRequireChangeId(InheritableBoolean value) throws Exception {
1075 4 : try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
1076 4 : ProjectConfig config = projectConfigFactory.read(md);
1077 4 : config.updateProject(p -> p.setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, value));
1078 4 : config.commit(md);
1079 4 : projectCache.evictAndReindex(config.getProject());
1080 : }
1081 4 : }
1082 :
1083 : protected void blockAnonymousRead() throws Exception {
1084 7 : String allRefs = RefNames.REFS + "*";
1085 7 : projectOperations
1086 7 : .project(project)
1087 7 : .forUpdate()
1088 7 : .add(block(Permission.READ).ref(allRefs).group(ANONYMOUS_USERS))
1089 7 : .add(allow(Permission.READ).ref(allRefs).group(REGISTERED_USERS))
1090 7 : .update();
1091 7 : }
1092 :
1093 : protected PushOneCommit.Result pushTo(String ref) throws Exception {
1094 18 : PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
1095 18 : return push.to(ref);
1096 : }
1097 :
1098 : protected void approve(String id) throws Exception {
1099 26 : gApi.changes().id(id).current().review(ReviewInput.approve());
1100 26 : }
1101 :
1102 : protected void recommend(String id) throws Exception {
1103 5 : gApi.changes().id(id).current().review(ReviewInput.recommend());
1104 5 : }
1105 :
1106 : protected void assertThatAccountIsNotVisible(TestAccount... testAccounts) {
1107 5 : for (TestAccount testAccount : testAccounts) {
1108 5 : assertThrows(
1109 0 : ResourceNotFoundException.class, () -> gApi.accounts().id(testAccount.id().get()).get());
1110 : }
1111 5 : }
1112 :
1113 : protected void assertReviewers(String changeId, TestAccount... expectedReviewers)
1114 : throws RestApiException {
1115 2 : Map<ReviewerState, Collection<AccountInfo>> reviewerMap =
1116 2 : gApi.changes().id(changeId).get().reviewers;
1117 2 : assertThat(reviewerMap).containsKey(ReviewerState.REVIEWER);
1118 2 : List<Integer> reviewers =
1119 2 : reviewerMap.get(ReviewerState.REVIEWER).stream().map(a -> a._accountId).collect(toList());
1120 2 : assertThat(reviewers)
1121 2 : .containsExactlyElementsIn(
1122 2 : Arrays.stream(expectedReviewers).map(a -> a.id().get()).collect(toList()));
1123 2 : }
1124 :
1125 : protected void assertCcs(String changeId, TestAccount... expectedCcs) throws RestApiException {
1126 2 : Map<ReviewerState, Collection<AccountInfo>> reviewerMap =
1127 2 : gApi.changes().id(changeId).get().reviewers;
1128 2 : assertThat(reviewerMap).containsKey(ReviewerState.CC);
1129 2 : List<Integer> ccs =
1130 2 : reviewerMap.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
1131 2 : assertThat(ccs)
1132 2 : .containsExactlyElementsIn(
1133 2 : Arrays.stream(expectedCcs).map(a -> a.id().get()).collect(toList()));
1134 2 : }
1135 :
1136 : protected void assertNoCcs(String changeId) throws RestApiException {
1137 1 : Map<ReviewerState, Collection<AccountInfo>> reviewerMap =
1138 1 : gApi.changes().id(changeId).get().reviewers;
1139 1 : assertThat(reviewerMap).doesNotContainKey(ReviewerState.CC);
1140 1 : }
1141 :
1142 : protected void assertSubmittedTogether(String chId, String... expected) throws Exception {
1143 2 : assertSubmittedTogether(chId, ImmutableSet.of(), expected);
1144 2 : }
1145 :
1146 : protected void assertSubmittedTogetherWithTopicClosure(String chId, String... expected)
1147 : throws Exception {
1148 1 : assertSubmittedTogether(chId, ImmutableSet.of(TOPIC_CLOSURE), expected);
1149 1 : }
1150 :
1151 : protected void assertSubmittedTogether(
1152 : String chId,
1153 : ImmutableSet<SubmittedTogetherOption> submittedTogetherOptions,
1154 : String... expected)
1155 : throws Exception {
1156 : // This does not include NON_VISIBILE_CHANGES
1157 : List<ChangeInfo> actual =
1158 2 : submittedTogetherOptions.isEmpty()
1159 2 : ? gApi.changes().id(chId).submittedTogether()
1160 1 : : gApi.changes()
1161 1 : .id(chId)
1162 2 : .submittedTogether(EnumSet.copyOf(submittedTogetherOptions))
1163 1 : .changes;
1164 :
1165 : EnumSet<SubmittedTogetherOption> enumSetIncludingNonVisibleChanges =
1166 2 : submittedTogetherOptions.isEmpty()
1167 2 : ? EnumSet.of(NON_VISIBLE_CHANGES)
1168 2 : : EnumSet.copyOf(submittedTogetherOptions);
1169 2 : enumSetIncludingNonVisibleChanges.add(NON_VISIBLE_CHANGES);
1170 :
1171 : // This includes NON_VISIBLE_CHANGES for comparison.
1172 2 : SubmittedTogetherInfo info =
1173 2 : gApi.changes().id(chId).submittedTogether(enumSetIncludingNonVisibleChanges);
1174 :
1175 2 : assertThat(info.nonVisibleChanges).isEqualTo(0);
1176 2 : assertThat(Iterables.transform(actual, i1 -> i1.changeId))
1177 2 : .containsExactly((Object[]) expected)
1178 2 : .inOrder();
1179 2 : assertThat(Iterables.transform(info.changes, i -> i.changeId))
1180 2 : .containsExactly((Object[]) expected)
1181 2 : .inOrder();
1182 2 : }
1183 :
1184 : protected PatchSet getPatchSet(PatchSet.Id psId) {
1185 0 : return changeDataFactory.create(project, psId.changeId()).patchSet(psId);
1186 : }
1187 :
1188 : protected IdentifiedUser user(TestAccount testAccount) {
1189 4 : return identifiedUserFactory.create(testAccount.id());
1190 : }
1191 :
1192 : protected RevisionResource parseCurrentRevisionResource(String changeId) throws Exception {
1193 6 : ChangeResource cr = parseChangeResource(changeId);
1194 6 : int psId = cr.getChange().currentPatchSetId().get();
1195 6 : return revisions.parse(cr, IdString.fromDecoded(Integer.toString(psId)));
1196 : }
1197 :
1198 : protected RevisionResource parseRevisionResource(String changeId, int n) throws Exception {
1199 0 : return revisions.parse(
1200 0 : parseChangeResource(changeId), IdString.fromDecoded(Integer.toString(n)));
1201 : }
1202 :
1203 : protected RevisionResource parseRevisionResource(PushOneCommit.Result r) throws Exception {
1204 0 : PatchSet.Id psId = r.getPatchSetId();
1205 0 : return parseRevisionResource(psId.changeId().toString(), psId.get());
1206 : }
1207 :
1208 : protected ChangeResource parseChangeResource(String changeId) throws Exception {
1209 8 : List<ChangeNotes> notes = changeFinder.find(changeId);
1210 8 : assertThat(notes).hasSize(1);
1211 8 : return changeResourceFactory.create(notes.get(0), atrScope.get().getUser());
1212 : }
1213 :
1214 : protected RevCommit getHead(Repository repo, String name) throws Exception {
1215 12 : try (RevWalk rw = new RevWalk(repo)) {
1216 12 : Ref r = repo.exactRef(name);
1217 12 : return rw.parseCommit(r.getObjectId());
1218 : }
1219 : }
1220 :
1221 : protected void assertMailReplyTo(Message message, String email) throws Exception {
1222 3 : assertThat(message.headers()).containsKey("Reply-To");
1223 3 : StringEmailHeader replyTo = (StringEmailHeader) message.headers().get("Reply-To");
1224 3 : assertThat(replyTo.getString()).contains(email);
1225 3 : }
1226 :
1227 : protected void assertMailNotReplyTo(Message message, String email) throws Exception {
1228 1 : assertThat(message.headers()).containsKey("Reply-To");
1229 1 : StringEmailHeader replyTo = (StringEmailHeader) message.headers().get("Reply-To");
1230 1 : assertThat(replyTo.getString()).doesNotContain(email);
1231 1 : }
1232 :
1233 : /** Assert that the given branches have the given tree ids. */
1234 : protected void assertTrees(Project.NameKey proj, Map<BranchNameKey, ObjectId> trees)
1235 : throws Exception {
1236 0 : TestRepository<?> localRepo = cloneProject(proj);
1237 0 : GitUtil.fetch(localRepo, "refs/*:refs/*");
1238 0 : Map<BranchNameKey, RevTree> refValues = new HashMap<>();
1239 :
1240 0 : for (BranchNameKey b : trees.keySet()) {
1241 0 : if (!b.project().equals(proj)) {
1242 0 : continue;
1243 : }
1244 :
1245 0 : Ref r = localRepo.getRepository().exactRef(b.branch());
1246 0 : assertThat(r).isNotNull();
1247 0 : RevWalk rw = localRepo.getRevWalk();
1248 0 : RevCommit c = rw.parseCommit(r.getObjectId());
1249 0 : refValues.put(b, c.getTree());
1250 :
1251 0 : assertThat(trees.get(b)).isEqualTo(refValues.get(b));
1252 0 : }
1253 0 : assertThat(refValues.keySet()).containsAnyIn(trees.keySet());
1254 0 : }
1255 :
1256 : protected void assertDiffForFullyModifiedFile(
1257 : DiffInfo diff,
1258 : String commitName,
1259 : String path,
1260 : String expectedContentSideA,
1261 : String expectedContentSideB)
1262 : throws Exception {
1263 1 : assertDiffForFile(diff, commitName, path);
1264 :
1265 1 : ImmutableList<String> expectedOldLines =
1266 1 : ImmutableList.copyOf(expectedContentSideA.split("\n", -1));
1267 1 : ImmutableList<String> expectedNewLines =
1268 1 : ImmutableList.copyOf(expectedContentSideB.split("\n", -1));
1269 :
1270 1 : assertThat(diff.changeType).isEqualTo(ChangeType.MODIFIED);
1271 :
1272 1 : assertThat(diff.metaA).isNotNull();
1273 1 : assertThat(diff.metaB).isNotNull();
1274 :
1275 1 : assertThat(diff.metaA.name).isEqualTo(path);
1276 1 : assertThat(diff.metaA.lines).isEqualTo(expectedOldLines.size());
1277 1 : assertThat(diff.metaB.name).isEqualTo(path);
1278 1 : assertThat(diff.metaB.lines).isEqualTo(expectedNewLines.size());
1279 :
1280 1 : DiffInfo.ContentEntry contentEntry = diff.content.get(0);
1281 1 : assertThat(contentEntry.a).containsExactlyElementsIn(expectedOldLines).inOrder();
1282 1 : assertThat(contentEntry.b).containsExactlyElementsIn(expectedNewLines).inOrder();
1283 1 : assertThat(contentEntry.ab).isNull();
1284 1 : assertThat(contentEntry.common).isNull();
1285 1 : assertThat(contentEntry.editA).isNull();
1286 1 : assertThat(contentEntry.editB).isNull();
1287 1 : assertThat(contentEntry.skip).isNull();
1288 1 : }
1289 :
1290 : protected void assertDiffForNewFile(
1291 : DiffInfo diff, @Nullable RevCommit commit, String path, String expectedContentSideB)
1292 : throws Exception {
1293 2 : assertDiffForNewFile(diff, commit.name(), path, expectedContentSideB);
1294 2 : }
1295 :
1296 : protected void assertDiffForNewFile(
1297 : DiffInfo diff, String commitName, String path, String expectedContentSideB) throws Exception {
1298 4 : assertDiffForFile(diff, commitName, path);
1299 :
1300 4 : ImmutableList<String> expectedLines =
1301 4 : ImmutableList.copyOf(expectedContentSideB.split("\n", -1));
1302 :
1303 4 : assertThat(diff.changeType).isEqualTo(ChangeType.ADDED);
1304 :
1305 4 : assertThat(diff.metaA).isNull();
1306 4 : assertThat(diff.metaB).isNotNull();
1307 :
1308 4 : assertThat(diff.metaB.name).isEqualTo(path);
1309 4 : assertThat(diff.metaB.lines).isEqualTo(expectedLines.size());
1310 :
1311 4 : DiffInfo.ContentEntry contentEntry = diff.content.get(0);
1312 4 : assertThat(contentEntry.b).containsExactlyElementsIn(expectedLines).inOrder();
1313 4 : assertThat(contentEntry.a).isNull();
1314 4 : assertThat(contentEntry.ab).isNull();
1315 4 : assertThat(contentEntry.common).isNull();
1316 4 : assertThat(contentEntry.editA).isNull();
1317 4 : assertThat(contentEntry.editB).isNull();
1318 4 : assertThat(contentEntry.skip).isNull();
1319 4 : }
1320 :
1321 : protected void assertDiffForDeletedFile(DiffInfo diff, String path, String expectedContentSideA)
1322 : throws Exception {
1323 1 : assertDiffHeaders(diff);
1324 :
1325 1 : ImmutableList<String> expectedOriginalLines =
1326 1 : ImmutableList.copyOf(expectedContentSideA.split("\n", -1));
1327 :
1328 1 : assertThat(diff.changeType).isEqualTo(ChangeType.DELETED);
1329 :
1330 1 : assertThat(diff.metaA).isNotNull();
1331 1 : assertThat(diff.metaB).isNull();
1332 :
1333 1 : assertThat(diff.metaA.name).isEqualTo(path);
1334 1 : assertThat(diff.metaA.lines).isEqualTo(expectedOriginalLines.size());
1335 :
1336 1 : DiffInfo.ContentEntry contentEntry = diff.content.get(0);
1337 1 : assertThat(contentEntry.a).containsExactlyElementsIn(expectedOriginalLines).inOrder();
1338 1 : assertThat(contentEntry.b).isNull();
1339 1 : assertThat(contentEntry.ab).isNull();
1340 1 : assertThat(contentEntry.common).isNull();
1341 1 : assertThat(contentEntry.editA).isNull();
1342 1 : assertThat(contentEntry.editB).isNull();
1343 1 : assertThat(contentEntry.skip).isNull();
1344 1 : }
1345 :
1346 : private void assertDiffForFile(DiffInfo diff, String commitName, String path) throws Exception {
1347 4 : assertDiffHeaders(diff);
1348 :
1349 4 : assertThat(diff.metaB.commitId).isEqualTo(commitName);
1350 :
1351 4 : String expectedContentType = "text/plain";
1352 4 : if (COMMIT_MSG.equals(path)) {
1353 1 : expectedContentType = FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE;
1354 4 : } else if (MERGE_LIST.equals(path)) {
1355 1 : expectedContentType = FileContentUtil.TEXT_X_GERRIT_MERGE_LIST;
1356 : }
1357 :
1358 4 : assertThat(diff.metaB.contentType).isEqualTo(expectedContentType);
1359 :
1360 4 : assertThat(diff.metaB.name).isEqualTo(path);
1361 4 : assertThat(diff.metaB.webLinks).isNull();
1362 4 : }
1363 :
1364 : private void assertDiffHeaders(DiffInfo diff) throws Exception {
1365 4 : assertThat(diff.binary).isNull();
1366 4 : assertThat(diff.diffHeader).isNotNull();
1367 4 : assertThat(diff.intralineStatus).isNull();
1368 4 : assertThat(diff.webLinks).isNull();
1369 4 : assertThat(diff.editWebLinks).isNull();
1370 4 : }
1371 :
1372 : protected void assertPermitted(ChangeInfo info, String label, Integer... expected) {
1373 3 : assertThat(info.permittedLabels).isNotNull();
1374 3 : Collection<String> strs = info.permittedLabels.get(label);
1375 3 : if (expected.length == 0) {
1376 2 : assertThat(strs).isNull();
1377 : } else {
1378 3 : assertThat(strs.stream().map(s -> Integer.valueOf(s.trim())).collect(toList()))
1379 3 : .containsExactlyElementsIn(Arrays.asList(expected));
1380 : }
1381 3 : }
1382 :
1383 : protected void assertPermissions(
1384 : Project.NameKey project,
1385 : GroupReference groupReference,
1386 : String ref,
1387 : boolean exclusive,
1388 : String... permissionNames) {
1389 3 : Optional<AccessSection> accessSection =
1390 : projectCache
1391 3 : .get(project)
1392 3 : .orElseThrow(illegalState(project))
1393 3 : .getConfig()
1394 3 : .getAccessSection(ref);
1395 3 : assertThat(accessSection).isPresent();
1396 3 : for (String permissionName : permissionNames) {
1397 3 : Permission permission = accessSection.get().getPermission(permissionName);
1398 3 : assertPermission(permission, permissionName, exclusive, null);
1399 3 : assertPermissionRule(
1400 3 : permission.getRule(groupReference), groupReference, Action.ALLOW, false, 0, 0);
1401 : }
1402 3 : }
1403 :
1404 : protected void assertPermission(
1405 : Permission permission,
1406 : String expectedName,
1407 : boolean expectedExclusive,
1408 : @Nullable String expectedLabelName) {
1409 3 : assertThat(permission).isNotNull();
1410 3 : assertThat(permission.getName()).isEqualTo(expectedName);
1411 3 : assertThat(permission.getExclusiveGroup()).isEqualTo(expectedExclusive);
1412 3 : assertThat(permission.getLabel()).isEqualTo(expectedLabelName);
1413 3 : }
1414 :
1415 : protected void assertPermissionRule(
1416 : PermissionRule rule,
1417 : GroupReference expectedGroupReference,
1418 : Action expectedAction,
1419 : boolean expectedForce,
1420 : int expectedMin,
1421 : int expectedMax) {
1422 3 : assertThat(rule.getGroup()).isEqualTo(expectedGroupReference);
1423 3 : assertThat(rule.getAction()).isEqualTo(expectedAction);
1424 3 : assertThat(rule.getForce()).isEqualTo(expectedForce);
1425 3 : assertThat(rule.getMin()).isEqualTo(expectedMin);
1426 3 : assertThat(rule.getMax()).isEqualTo(expectedMax);
1427 3 : }
1428 :
1429 : protected void assertHead(String projectName, String expectedRef) throws Exception {
1430 : // Assert gerrit's project head points to the correct branch
1431 3 : assertThat(getProjectBranches(projectName).get(Constants.HEAD).revision)
1432 3 : .isEqualTo(RefNames.shortName(expectedRef));
1433 : // Assert git head points to the correct branch
1434 3 : try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) {
1435 3 : assertThat(repo.exactRef(Constants.HEAD).getTarget().getName()).isEqualTo(expectedRef);
1436 : }
1437 3 : }
1438 :
1439 : protected InternalGroup group(AccountGroup.UUID groupUuid) {
1440 1 : InternalGroup group = groupCache.get(groupUuid).orElse(null);
1441 1 : assertWithMessage(groupUuid.get()).that(group).isNotNull();
1442 1 : return group;
1443 : }
1444 :
1445 : protected GroupReference groupRef(AccountGroup.UUID groupUuid) {
1446 3 : GroupDescription.Basic groupDescription = groupBackend.get(groupUuid);
1447 3 : return GroupReference.create(groupDescription.getGroupUUID(), groupDescription.getName());
1448 : }
1449 :
1450 : protected InternalGroup group(String groupName) {
1451 32 : InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null);
1452 32 : assertWithMessage(groupName).that(group).isNotNull();
1453 32 : return group;
1454 : }
1455 :
1456 : protected GroupReference groupRef(String groupName) {
1457 1 : InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null);
1458 1 : assertThat(group).isNotNull();
1459 1 : return GroupReference.create(group.getGroupUUID(), group.getName());
1460 : }
1461 :
1462 : protected AccountGroup.UUID groupUuid(String groupName) {
1463 31 : return group(groupName).getGroupUUID();
1464 : }
1465 :
1466 : protected InternalGroup adminGroup() {
1467 2 : return group("Administrators");
1468 : }
1469 :
1470 : protected GroupReference adminGroupRef() {
1471 1 : return groupRef("Administrators");
1472 : }
1473 :
1474 : protected AccountGroup.UUID adminGroupUuid() {
1475 30 : return groupUuid("Administrators");
1476 : }
1477 :
1478 : protected void assertGroupDoesNotExist(String groupName) {
1479 1 : InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null);
1480 1 : assertWithMessage(groupName).that(group).isNull();
1481 1 : }
1482 :
1483 : protected void assertNotifyTo(TestAccount expected) {
1484 5 : assertNotifyTo(expected.email(), expected.fullName());
1485 5 : }
1486 :
1487 : protected void assertNotifyTo(String expectedEmail, String expectedFullname) {
1488 6 : Address expectedAddress = Address.create(expectedFullname, expectedEmail);
1489 6 : assertThat(sender.getMessages()).hasSize(1);
1490 6 : Message m = sender.getMessages().get(0);
1491 6 : assertThat(m.rcpt()).containsExactly(expectedAddress);
1492 6 : assertThat(((EmailHeader.AddressList) m.headers().get("To")).getAddressList())
1493 6 : .containsExactly(expectedAddress);
1494 6 : assertThat(m.headers().get("Cc").isEmpty()).isTrue();
1495 6 : }
1496 :
1497 : protected void assertNotifyCc(TestAccount expected) {
1498 4 : assertNotifyCc(expected.getNameEmail());
1499 4 : }
1500 :
1501 : protected void assertNotifyCc(String expectedEmail, String expectedFullname) {
1502 1 : Address expectedAddress = Address.create(expectedFullname, expectedEmail);
1503 1 : assertNotifyCc(expectedAddress);
1504 1 : }
1505 :
1506 : protected void assertNotifyCc(Address expectedAddress) {
1507 6 : assertThat(sender.getMessages()).hasSize(1);
1508 6 : Message m = sender.getMessages().get(0);
1509 6 : assertThat(m.rcpt()).containsExactly(expectedAddress);
1510 6 : assertThat(m.headers().get("To").isEmpty()).isTrue();
1511 6 : assertThat(((EmailHeader.AddressList) m.headers().get("Cc")).getAddressList())
1512 6 : .containsExactly(expectedAddress);
1513 6 : }
1514 :
1515 : protected void assertNotifyBcc(TestAccount expected) {
1516 3 : assertThat(sender.getMessages()).hasSize(1);
1517 3 : Message m = sender.getMessages().get(0);
1518 3 : assertThat(m.rcpt()).containsExactly(expected.getNameEmail());
1519 3 : assertThat(m.headers().get("To").isEmpty()).isTrue();
1520 3 : assertThat(m.headers().get("Cc").isEmpty()).isTrue();
1521 3 : }
1522 :
1523 : protected void assertNotifyBcc(String expectedEmail, String expectedFullName) {
1524 1 : assertThat(sender.getMessages()).hasSize(1);
1525 1 : Message m = sender.getMessages().get(0);
1526 1 : assertThat(m.rcpt()).containsExactly(Address.create(expectedFullName, expectedEmail));
1527 1 : assertThat(m.headers().get("To").isEmpty()).isTrue();
1528 1 : assertThat(m.headers().get("Cc").isEmpty()).isTrue();
1529 1 : }
1530 :
1531 : protected interface ProjectWatchInfoConfiguration {
1532 : void configure(ProjectWatchInfo pwi);
1533 : }
1534 :
1535 : protected void watch(String project, ProjectWatchInfoConfiguration config)
1536 : throws RestApiException {
1537 4 : ProjectWatchInfo pwi = new ProjectWatchInfo();
1538 4 : pwi.project = project;
1539 4 : config.configure(pwi);
1540 4 : gApi.accounts().self().setWatchedProjects(ImmutableList.of(pwi));
1541 4 : }
1542 :
1543 : protected void watch(PushOneCommit.Result r, ProjectWatchInfoConfiguration config)
1544 : throws RestApiException {
1545 0 : watch(r.getChange().project().get(), config);
1546 0 : }
1547 :
1548 : protected void watch(String project, String filter) throws RestApiException {
1549 3 : watch(
1550 : project,
1551 : pwi -> {
1552 3 : pwi.filter = filter;
1553 3 : pwi.notifyAbandonedChanges = true;
1554 3 : pwi.notifyNewChanges = true;
1555 3 : pwi.notifyNewPatchSets = true;
1556 3 : pwi.notifyAllComments = true;
1557 3 : });
1558 3 : }
1559 :
1560 : protected void watch(String project) throws RestApiException {
1561 3 : watch(project, (String) null);
1562 3 : }
1563 :
1564 : protected void assertContent(PushOneCommit.Result pushResult, String path, String expectedContent)
1565 : throws Exception {
1566 2 : BinaryResult bin =
1567 2 : gApi.changes()
1568 2 : .id(pushResult.getChangeId())
1569 2 : .revision(pushResult.getCommit().name())
1570 2 : .file(path)
1571 2 : .content();
1572 2 : ByteArrayOutputStream os = new ByteArrayOutputStream();
1573 2 : bin.writeTo(os);
1574 2 : String res = new String(os.toByteArray(), UTF_8);
1575 2 : assertThat(res).isEqualTo(expectedContent);
1576 2 : }
1577 :
1578 : protected RevCommit createNewCommitWithoutChangeId(String branch, String file, String content)
1579 : throws Exception {
1580 2 : try (Repository repo = repoManager.openRepository(project);
1581 2 : RevWalk walk = new RevWalk(repo)) {
1582 2 : Ref ref = repo.exactRef(branch);
1583 2 : RevCommit tip = null;
1584 2 : if (ref != null) {
1585 2 : tip = walk.parseCommit(ref.getObjectId());
1586 : }
1587 2 : TestRepository<?> testSrcRepo = new TestRepository<>(repo);
1588 2 : TestRepository<?>.BranchBuilder builder = testSrcRepo.branch(branch);
1589 : RevCommit revCommit =
1590 2 : tip == null
1591 1 : ? builder.commit().message("commit 1").add(file, content).create()
1592 2 : : builder.commit().parent(tip).message("commit 1").add(file, content).create();
1593 2 : assertThat(GitUtil.getChangeId(testSrcRepo, revCommit)).isEmpty();
1594 2 : return revCommit;
1595 : }
1596 : }
1597 :
1598 : protected RevCommit parseCurrentRevision(RevWalk rw, String changeId) throws Exception {
1599 7 : return rw.parseCommit(
1600 7 : ObjectId.fromString(get(changeId, ListChangesOption.CURRENT_REVISION).currentRevision));
1601 : }
1602 :
1603 : protected void configSubmitRequirement(
1604 : Project.NameKey project, SubmitRequirement submitRequirement) throws Exception {
1605 3 : try (ProjectConfigUpdate u = updateProject(project)) {
1606 3 : u.getConfig().upsertSubmitRequirement(submitRequirement);
1607 3 : u.save();
1608 : }
1609 3 : }
1610 :
1611 : protected void clearSubmitRequirements(Project.NameKey project) throws Exception {
1612 1 : try (ProjectConfigUpdate u = updateProject(project)) {
1613 1 : u.getConfig().clearSubmitRequirements();
1614 1 : u.save();
1615 : }
1616 1 : }
1617 :
1618 : protected void configLabel(String label, LabelFunction func) throws Exception {
1619 8 : configLabel(label, func, ImmutableList.of());
1620 8 : }
1621 :
1622 : protected void configLabel(String label, LabelFunction func, List<String> refPatterns)
1623 : throws Exception {
1624 8 : configLabel(
1625 : project,
1626 : label,
1627 : func,
1628 : refPatterns,
1629 8 : value(1, "Passes"),
1630 8 : value(0, "No score"),
1631 8 : value(-1, "Failed"));
1632 8 : }
1633 :
1634 : protected void configLabel(
1635 : Project.NameKey project, String label, LabelFunction func, LabelValue... value)
1636 : throws Exception {
1637 3 : configLabel(project, label, func, ImmutableList.of(), value);
1638 3 : }
1639 :
1640 : private void configLabel(
1641 : Project.NameKey project,
1642 : String label,
1643 : LabelFunction func,
1644 : List<String> refPatterns,
1645 : LabelValue... value)
1646 : throws Exception {
1647 9 : try (ProjectConfigUpdate u = updateProject(project)) {
1648 9 : LabelType.Builder labelType = label(label, value).toBuilder();
1649 9 : labelType.setFunction(func);
1650 9 : labelType.setRefPatterns(ImmutableList.copyOf(refPatterns));
1651 9 : u.getConfig().upsertLabelType(labelType.build());
1652 9 : u.save();
1653 : }
1654 9 : }
1655 :
1656 : protected void enableCreateNewChangeForAllNotInTarget() throws Exception {
1657 11 : try (ProjectConfigUpdate u = updateProject(project)) {
1658 11 : u.getConfig()
1659 11 : .updateProject(
1660 : p ->
1661 11 : p.setBooleanConfig(
1662 : BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
1663 : InheritableBoolean.TRUE));
1664 11 : u.save();
1665 : }
1666 11 : }
1667 :
1668 : protected ProjectConfigUpdate updateProject(Project.NameKey projectName) throws Exception {
1669 44 : return new ProjectConfigUpdate(projectName);
1670 : }
1671 :
1672 : protected class ProjectConfigUpdate implements AutoCloseable {
1673 : private final ProjectConfig projectConfig;
1674 : private MetaDataUpdate metaDataUpdate;
1675 :
1676 44 : private ProjectConfigUpdate(Project.NameKey projectName) throws Exception {
1677 44 : metaDataUpdate = metaDataUpdateFactory.create(projectName);
1678 44 : projectConfig = projectConfigFactory.read(metaDataUpdate);
1679 44 : }
1680 :
1681 : public ProjectConfig getConfig() {
1682 44 : return projectConfig;
1683 : }
1684 :
1685 : public void save() throws Exception {
1686 44 : metaDataUpdate.setAuthor(identifiedUserFactory.create(admin.id()));
1687 44 : projectConfig.commit(metaDataUpdate);
1688 44 : metaDataUpdate.close();
1689 44 : metaDataUpdate = null;
1690 44 : projectCache.evictAndReindex(projectConfig.getProject());
1691 44 : }
1692 :
1693 : @Override
1694 : public void close() {
1695 44 : if (metaDataUpdate != null) {
1696 0 : metaDataUpdate.close();
1697 : }
1698 44 : }
1699 : }
1700 :
1701 : protected List<RevCommit> getChangeMetaCommitsInReverseOrder(Change.Id changeId)
1702 : throws IOException {
1703 5 : try (Repository repo = repoManager.openRepository(project);
1704 5 : RevWalk revWalk = new RevWalk(repo)) {
1705 5 : revWalk.sort(RevSort.TOPO);
1706 5 : revWalk.sort(RevSort.REVERSE);
1707 5 : Ref metaRef = repo.exactRef(RefNames.changeMetaRef(changeId));
1708 5 : revWalk.markStart(revWalk.parseCommit(metaRef.getObjectId()));
1709 5 : return Lists.newArrayList(revWalk);
1710 : }
1711 : }
1712 :
1713 : protected List<CommentInfo> getChangeSortedComments(int changeNum) throws Exception {
1714 2 : List<CommentInfo> comments = new ArrayList<>();
1715 2 : Map<String, List<CommentInfo>> commentsMap =
1716 2 : gApi.changes().id(changeNum).commentsRequest().get();
1717 2 : for (Map.Entry<String, List<CommentInfo>> e : commentsMap.entrySet()) {
1718 2 : for (CommentInfo c : e.getValue()) {
1719 2 : c.path = e.getKey(); // Set the comment's path field.
1720 2 : comments.add(c);
1721 2 : }
1722 2 : }
1723 2 : comments.sort(Comparator.comparing(c -> c.id));
1724 2 : return comments;
1725 : }
1726 :
1727 : protected ImmutableMap<String, BranchInfo> getProjectBranches(String projectName)
1728 : throws RestApiException {
1729 3 : return gApi.projects().name(projectName).branches().get().stream()
1730 3 : .collect(ImmutableMap.toImmutableMap(branch -> branch.ref, branch -> branch));
1731 : }
1732 :
1733 : protected AutoCloseable installPlugin(String pluginName, Class<? extends Module> sysModuleClass)
1734 : throws Exception {
1735 7 : return installPlugin(pluginName, sysModuleClass, null, null);
1736 : }
1737 :
1738 : protected AutoCloseable installPlugin(
1739 : String pluginName,
1740 : @Nullable Class<? extends Module> sysModuleClass,
1741 : @Nullable Class<? extends Module> httpModuleClass,
1742 : @Nullable Class<? extends Module> sshModuleClass)
1743 : throws Exception {
1744 8 : checkStatic(sysModuleClass);
1745 8 : checkStatic(httpModuleClass);
1746 8 : checkStatic(sshModuleClass);
1747 8 : TestServerPlugin plugin =
1748 : new TestServerPlugin(
1749 : pluginName,
1750 : "http://example.com/" + pluginName,
1751 8 : pluginUserFactory.create(pluginName),
1752 8 : getClass().getClassLoader(),
1753 8 : sysModuleClass != null ? sysModuleClass.getName() : null,
1754 8 : httpModuleClass != null ? httpModuleClass.getName() : null,
1755 8 : sshModuleClass != null ? sshModuleClass.getName() : null,
1756 8 : sitePaths.data_dir.resolve(pluginName));
1757 8 : plugin.start(pluginGuiceEnvironment);
1758 8 : pluginGuiceEnvironment.onStartPlugin(plugin);
1759 8 : return () -> {
1760 8 : plugin.stop(pluginGuiceEnvironment);
1761 8 : pluginGuiceEnvironment.onStopPlugin(plugin);
1762 8 : };
1763 : }
1764 :
1765 : private static void checkStatic(@Nullable Class<? extends Module> moduleClass) {
1766 8 : if (moduleClass != null) {
1767 8 : checkArgument(
1768 8 : (moduleClass.getModifiers() & Modifier.STATIC) != 0,
1769 : "module must be static: %s",
1770 8 : moduleClass.getName());
1771 : }
1772 8 : }
1773 :
1774 : /** {@link Ticker} implementation for mocking without restarting GerritServer */
1775 : public static class TestTicker extends Ticker {
1776 : Ticker actualTicker;
1777 :
1778 138 : public TestTicker() {
1779 138 : useDefaultTicker();
1780 138 : }
1781 :
1782 : /** Switches to system ticker */
1783 : public Ticker useDefaultTicker() {
1784 138 : this.actualTicker = Ticker.systemTicker();
1785 138 : return actualTicker;
1786 : }
1787 :
1788 : /** Switches to {@link FakeTicker} */
1789 : public FakeTicker useFakeTicker() {
1790 1 : if (!(this.actualTicker instanceof FakeTicker)) {
1791 1 : this.actualTicker = new FakeTicker();
1792 : }
1793 1 : return (FakeTicker) actualTicker;
1794 : }
1795 :
1796 : @Override
1797 : public long read() {
1798 97 : return actualTicker.read();
1799 : }
1800 : }
1801 : }
|