package com.hubspot.blazar.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jukito.JukitoRunner;
import org.jukito.UseModules;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kohsuke.github.BlazarGHChange;
import org.kohsuke.github.BlazarGHCommit;
import org.kohsuke.github.BlazarGHCommitFile;
import org.kohsuke.github.BlazarGHCommitShortInfo;
import org.kohsuke.github.BlazarGHContent;
import org.kohsuke.github.BlazarGHRepository;
import org.kohsuke.github.BlazarGHTreeEntry;
import org.kohsuke.github.BlazarGitUser;
import org.kohsuke.github.GitHub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.hubspot.blazar.BlazarServiceTestBase;
import com.hubspot.blazar.BlazarServiceTestModule;
import com.hubspot.blazar.base.BuildOptions;
import com.hubspot.blazar.base.BuildTrigger;
import com.hubspot.blazar.base.DependencyGraph;
import com.hubspot.blazar.base.GitInfo;
import com.hubspot.blazar.base.InterProjectBuild;
import com.hubspot.blazar.base.InterProjectBuildMapping;
import com.hubspot.blazar.base.Module;
import com.hubspot.blazar.base.RepositoryBuild;
import com.hubspot.blazar.data.service.BranchService;
import com.hubspot.blazar.data.service.DependenciesService;
import com.hubspot.blazar.data.service.InterProjectBuildMappingService;
import com.hubspot.blazar.data.service.InterProjectBuildService;
import com.hubspot.blazar.data.service.ModuleService;
import com.hubspot.blazar.externalservice.BuildClusterService;
import com.hubspot.blazar.util.TestBuildClusterService;
import io.dropwizard.db.ManagedDataSource;
@RunWith(JukitoRunner.class)
@UseModules({BlazarServiceTestModule.class})
public class InterProjectBuildServiceTest extends BlazarServiceTestBase {
private static final Logger LOG = LoggerFactory.getLogger(InterProjectBuildServiceTest.class);
@Inject
private BranchService branchService;
@Inject
private DependenciesService dependenciesService;
@Inject
private ModuleService moduleService;
@Inject
private InterProjectBuildService interProjectBuildService;
@Inject
private InterProjectBuildMappingService interProjectBuildMappingService;
@Inject
private BuildClusterService buildClusterService;
@Inject
private TestUtils testUtils;
@Inject
private Map<String, GitHub> gitHubMap;
@Before
public void before(ManagedDataSource dataSource) throws Exception {
// set up the data for these inter-project build tests
runSql(dataSource, "InterProjectData.sql");
}
@Test
public void itProducesTheRightTopologicalSort() {
Optional<Module> module1 = moduleService.get(1);
DependencyGraph graph = dependenciesService.buildInterProjectDependencyGraph(Sets.newHashSet(module1.get()));
assertThat(Arrays.asList(1, 4, 7, 8, 10, 11, 9, 13)).isEqualTo(graph.getTopologicalSort());
}
@Test
public void itCreatesTheCorrectInterProjectBuildMapping() {
long mappingId = interProjectBuildMappingService.insert(InterProjectBuildMapping.makeNewMapping(1, 2, Optional.of(3L), 4));
InterProjectBuildMapping createdMapping = interProjectBuildMappingService.getByMappingId(mappingId).get();
assertThat(new InterProjectBuildMapping(Optional.of(mappingId), 123, 123, Optional.of(3L), 123, Optional.<Long>absent(), InterProjectBuild.State.QUEUED)).isEqualTo(createdMapping);
}
@Test
public void itRunsASuccessfulInterProjectBuild() throws Exception {
((TestBuildClusterService) buildClusterService).clearModulesToFail();
// Trigger interProjectBuild
InterProjectBuild testableBuild = testUtils.runInterProjectBuild(1, Optional.<BuildTrigger>absent());
long buildId = testableBuild.getId().get();
assertThat(Sets.newHashSet(1)).isEqualTo(testableBuild.getModuleIds());
assertThat(InterProjectBuild.State.SUCCEEDED).isEqualTo(testableBuild.getState());
assertThat(Arrays.asList(1, 4, 7, 8, 10, 11, 9, 13)).isEqualTo(interProjectBuildService.getWithId(buildId).get().getDependencyGraph().get().getTopologicalSort());
Set<InterProjectBuildMapping> mappings = interProjectBuildMappingService.getMappingsForInterProjectBuild(buildId);
for (InterProjectBuildMapping mapping : mappings) {
assertThat(InterProjectBuild.State.SUCCEEDED).isEqualTo(mapping.getState());
}
// ensure there is 1 repo build for these modules
Set<Long> repoBuildIds = new HashSet<>();
Set<Integer> modulesInOneBuild = ImmutableSet.of(7,8,9);
for (InterProjectBuildMapping mapping : mappings) {
if (modulesInOneBuild.contains(mapping.getModuleId())) {
repoBuildIds.add(mapping.getRepoBuildId().get());
}
}
assertThat(repoBuildIds.size()).isEqualTo(1);
}
@Test
public void itGroupsModulesWhenBuildingOnManualTrigger() throws InterruptedException {
((TestBuildClusterService) buildClusterService).clearModulesToFail();
// Trigger interProjectBuild
InterProjectBuild testableBuild = testUtils.runInterProjectBuild(7, Optional.<BuildTrigger>absent());
long buildId = testableBuild.getId().get();
assertThat(Sets.newHashSet(7)).isEqualTo(testableBuild.getModuleIds());
assertThat(InterProjectBuild.State.SUCCEEDED).isEqualTo(testableBuild.getState());
Set<InterProjectBuildMapping> mappings = interProjectBuildMappingService.getMappingsForInterProjectBuild(buildId);
// ensure there is 1 repo build for these modules
Set<Long> repoBuildIds = new HashSet<>();
Set<Integer> modulesInOneBuild = ImmutableSet.of(7,8,9);
for (InterProjectBuildMapping mapping : mappings) {
if (modulesInOneBuild.contains(mapping.getModuleId())) {
repoBuildIds.add(mapping.getRepoBuildId().get());
}
}
assertThat(repoBuildIds.size()).isEqualTo(1);
}
@Test
public void itGroupsModulesWhenBuildingOnPush() throws Exception {
int repoId = 3;
String sha = "2222222222222222222222222222222222222222";
// ensure we have a previous build
GitInfo gitInfo = branchService.get(repoId).get();
testUtils.runDefaultRepositoryBuild(gitInfo);
// "commit" a change
GitHub testhost = gitHubMap.get("git.example.com");
BlazarGHRepository repo = (BlazarGHRepository) testhost.getRepository("test/Repo3");
BlazarGHCommitFile file = new BlazarGHCommitFile("/Module1/.blazar.yaml", BlazarGHCommitFile.Status.modified);
BlazarGitUser user = new BlazarGitUser("testy", "testy@example.com", 1465954246782L);
BlazarGHCommitShortInfo info = new BlazarGHCommitShortInfo("change file", user, user);
BlazarGHTreeEntry entry = new BlazarGHTreeEntry(sha, BlazarGHContent.fromString("git/example/com/Repo3/Module1/.blazar2.yaml"), "/Module1/.blazar.yaml");
BlazarGHCommit commit = new BlazarGHCommit(sha, ImmutableList.of(file), info);
BlazarGHChange change = new BlazarGHChange(commit, ImmutableList.of(entry), "master");
repo.applyChange(change);
BuildTrigger buildTrigger = BuildTrigger.forCommit(sha);
BuildOptions buildOptions = new BuildOptions(ImmutableSet.<Integer>of(), BuildOptions.BuildDownstreams.INTER_PROJECT, false);
RepositoryBuild repositoryBuild = testUtils.runAndWaitForRepositoryBuild(gitInfo, buildTrigger, buildOptions);
Set<InterProjectBuildMapping> mappings = interProjectBuildMappingService.getByRepoBuildId(repositoryBuild.getId().get());
// Here the mappings we have are only for the 1 repo we triggered (and queried on), have to get them all before we can test.
mappings = interProjectBuildMappingService.getMappingsForInterProjectBuild(mappings.iterator().next().getInterProjectBuildId());
// ensure there is 1 repo build for these modules
Set<Long> repoBuildIds = new HashSet<>();
Set<Integer> modulesInOneBuild = ImmutableSet.of(7,8,9);
for (InterProjectBuildMapping mapping : mappings) {
if (modulesInOneBuild.contains(mapping.getModuleId())) {
repoBuildIds.add(mapping.getRepoBuildId().get());
}
}
assertThat(repoBuildIds.size()).isEqualTo(1);
repo.revertLastChange();
}
@Test
public void itBuildsAnInterProjectBuildOnPush() throws Exception {
int repoId = 1;
String sha = "0000000000000000000000000000000000000000";
GitInfo gitInfo = branchService.get(repoId).get();
BuildTrigger buildTrigger = BuildTrigger.forCommit(sha);
BuildOptions buildOptions = new BuildOptions(ImmutableSet.of(), BuildOptions.BuildDownstreams.INTER_PROJECT, false);
RepositoryBuild repositoryBuild = testUtils.runAndWaitForRepositoryBuild(gitInfo, buildTrigger, buildOptions);
Set<InterProjectBuildMapping> interProjectBuildMappings = interProjectBuildMappingService.getByRepoBuildId(repositoryBuild.getId().get());
if (interProjectBuildMappings.isEmpty()) {
fail(String.format("Expected to have mappings for repo build %s", repositoryBuild));
}
Optional<InterProjectBuild> interProjectBuild = interProjectBuildService.getWithId(interProjectBuildMappings.iterator().next().getInterProjectBuildId());
if (!interProjectBuild.isPresent()) {
fail(String.format("InterProjectBuild for mapping %s should be present", interProjectBuildMappings.iterator().next()));
}
InterProjectBuild completedBuild = testUtils.waitForInterProjectBuild(interProjectBuild.get());
assertThat(Sets.newHashSet(1, 2, 3)).isEqualTo(completedBuild.getModuleIds());
assertThat(Arrays.asList(1, 2, 4, 5, 7, 6, 8, 10, 11, 9, 13)).isEqualTo(completedBuild.getDependencyGraph().get().getTopologicalSort());
assertThat(completedBuild.getState()).isEqualTo(InterProjectBuild.State.SUCCEEDED);
}
@Test
public void itCancelsDownstreamBuildsForInterProjectBuildWithFailures() throws Exception {
Set<Integer> expectedFailures = Sets.newHashSet(7);
Set<Integer> expectedSuccess = Sets.newHashSet(1, 4);
Set<Integer> expectedCancel = Sets.newHashSet(8, 9, 10, 11, 13);
((TestBuildClusterService) buildClusterService).setModulesToFail(expectedFailures);
// Cause module #10 to fail, causing 13 to be cancelled
InterProjectBuild testableBuild = testUtils.runInterProjectBuild(1, Optional.absent());
InterProjectBuild buildRun = interProjectBuildService.getWithId(testableBuild.getId().get()).get();
assertThat(buildRun.getState()).isEqualTo(InterProjectBuild.State.FAILED);
Set<InterProjectBuildMapping> mappings = interProjectBuildMappingService.getMappingsForInterProjectBuild(testableBuild.getId().get());
for (InterProjectBuildMapping mapping : mappings) {
if (expectedFailures.contains(mapping.getModuleId())) {
assertThat(mapping.getState().equals(InterProjectBuild.State.FAILED));
} else if (expectedCancel.contains(mapping.getModuleId())) {
assertThat(mapping.getState().equals(InterProjectBuild.State.CANCELLED));
} else if (expectedSuccess.contains(mapping.getModuleId())) {
assertThat(mapping.getState().equals(InterProjectBuild.State.SUCCEEDED));
}
}
((TestBuildClusterService) buildClusterService).clearModulesToFail();
}
}