blob: e4fd07ce30747927136f293f28f443bd90825187 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.ci.tcbot.chain;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.inject.Guice;
import com.google.inject.Injector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.apache.ignite.ci.ITeamcity;
import org.apache.ignite.ci.analysis.RunStat;
import org.apache.ignite.ci.tcmodel.conf.BuildType;
import org.apache.ignite.ci.tcmodel.hist.BuildRef;
import org.apache.ignite.ci.tcmodel.result.Build;
import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrence;
import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrence;
import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull;
import org.apache.ignite.ci.tcmodel.result.tests.TestRef;
import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
import org.apache.ignite.ci.teamcity.ignited.TeamcityIgnitedProviderMock;
import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
import org.apache.ignite.ci.teamcity.ignited.runhist.InvocationData;
import org.apache.ignite.ci.user.ICredentialsProv;
import org.apache.ignite.ci.web.model.current.SuiteCurrentStatus;
import org.apache.ignite.ci.web.model.current.TestFailure;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
/**
* Unit test for {@link PrChainsProcessor} and blockers detection. Emulates builds using Mockito. Does not start an
* Ignite node.
*/
public class PrChainsProcessorTest {
public static final String SRV_ID = "apache";
public static final String TEST_WITH_HISTORY_FAILING_IN_MASTER = "testWithHistoryFailingInMaster";
public static final String TEST_WITH_HISTORY_PASSING_IN_MASTER = "testWithHistoryPassingInMaster";
/** Test which is flaky in master, fail rate ~50%. */
public static final String TEST_FLAKY_IN_MASTER = "testFlaky50";
public static final String TEST_WITHOUT_HISTORY = "testWithoutHistory";
public static final String TEST_WAS_FIXED_IN_MASTER = "testFailingButFixedInMaster";
public static final int NUM_OF_TESTS_IN_MASTER = 10;
public static final String CACHE_1 = "Cache1";
/** Cache 9: Used to test flaky and non flaky tests detection. */
public static final String CACHE_9 = "Cache9";
/** Test rare failed with changes in build: should not be considered flaky in master and should became blocker. */
public static final String TEST_RARE_FAILED_WITH_CHANGES = "testWithRareFailuresWithChanges";
/** Test rare failed without any changes in build: should be considered flaky, and will not appear as blocker. */
public static final String TEST_RARE_FAILED_WITHOUT_CHANGES = "testWithRareFailuresWithoutChanges";
/** Build apache ignite: in some tests may contains compilation error, should became a blocker. */
public static final String BUILD_APACHE_IGNITE = "Build";
/** Cache 8: Used to test templates detection for PR branch. */
public static final String CACHE_8 = "Cache8";
/** Test, which was passing but became failed in branch. */
public static final String TEST_BECAME_FAILED_IN_BRANCH = "testBecameFailedInBranch";
/** Builds emulated storage. */
private Map<Integer, FatBuildCompacted> apacheBuilds = new ConcurrentHashMap<>();
/**
* Injector.
*/
private Injector injector = Guice.createInjector(new MockBasedTcBotModule());
/** */
@Before
public void initBuilds() {
final TeamcityIgnitedProviderMock instance = (TeamcityIgnitedProviderMock)injector.getInstance(ITeamcityIgnitedProvider.class);
instance.addServer(SRV_ID, apacheBuilds);
}
@Test
public void testTestFailureWithoutStatReportedAsBlocker() {
IStringCompactor c = injector.getInstance(IStringCompactor.class);
final String btId = "RunAll";
final String branch = "ignite-9542";
initBuildChainAndMasterHistory(c, btId, branch);
PrChainsProcessor prcp = injector.getInstance(PrChainsProcessor.class);
final List<SuiteCurrentStatus> blockers = prcp.getBlockersSuitesStatuses(btId,
branch, SRV_ID, mock(ICredentialsProv.class));
System.out.println(blockers);
assertNotNull(blockers);
assertFalse(blockers.isEmpty());
assertTrue(containsTestFailure(blockers, TEST_WITHOUT_HISTORY));
assertTrue(blockers.stream().anyMatch(s -> BUILD_APACHE_IGNITE.equals(s.suiteId)));
assertTrue(blockers.stream().anyMatch(s -> "CancelledBuild".equals(s.suiteId)));
assertTrue(containsTestFailure(blockers, TEST_WITH_HISTORY_PASSING_IN_MASTER));
assertFalse(containsTestFailure(blockers, TEST_WITH_HISTORY_FAILING_IN_MASTER));
assertFalse(containsTestFailure(blockers, TEST_FLAKY_IN_MASTER));
Optional<TestFailure> testOpt = findBlockerTestFailure(blockers, TEST_WITH_HISTORY_PASSING_IN_MASTER);
assertTrue(testOpt.isPresent());
assertTrue(containsTestFailure(blockers, TEST_WAS_FIXED_IN_MASTER));
assertFalse(containsTestFailure(blockers, TEST_WITH_HISTORY_FAILING_IN_MASTER));
// otherwise this non-blocker will not be filtered out
assertTrue(containsTestFailure(blockers, TEST_WITH_HISTORY_PASSING_IN_MASTER));
}
public boolean containsTestFailure(List<SuiteCurrentStatus> blockers, String name) {
return blockers.stream().anyMatch(containsTestFail(name));
}
/**
* Tests flaky detection mechanism. If master failures contained changes this test is non flaky. If test changes its
* status wihtout code modificaitons this test should be marged as flaky.
*/
@Test
public void testFlakyDetector() {
IStringCompactor c = injector.getInstance(IStringCompactor.class);
final String btId = "RunAll";
final String branch = "ignite-10056";
initChainForFlakyTest(c, btId, branch);
initHistory(c);
PrChainsProcessor prcp = injector.getInstance(PrChainsProcessor.class);
final List<SuiteCurrentStatus> blockers = prcp.getBlockersSuitesStatuses(btId,
branch, SRV_ID, mock(ICredentialsProv.class));
System.out.println(blockers);
Optional<TestFailure> rareNotFlaky = findBlockerTestFailure(blockers, TEST_RARE_FAILED_WITH_CHANGES);
assertTrue(rareNotFlaky.isPresent());
assertNull(rareNotFlaky.get().histBaseBranch.flakyComments);
assertFalse(findBlockerTestFailure(blockers, TEST_RARE_FAILED_WITHOUT_CHANGES).isPresent());
}
public Optional<TestFailure> findBlockerTestFailure(List<SuiteCurrentStatus> blockers, String name) {
Optional<SuiteCurrentStatus> suiteOpt = blockers.stream().filter(containsTestFail(name)).findAny();
return suiteOpt.flatMap(suite -> suite.testFailures.stream().filter(tf -> name.equals(tf.name)).findAny());
}
/**
* @param name Test failure Name to find.
*/
@NotNull
private Predicate<SuiteCurrentStatus> containsTestFail(String name) {
return s -> s.testFailures.stream().anyMatch(testFailure -> {
return name.equals(testFailure.name);
});
}
public void initBuildChainAndMasterHistory(IStringCompactor c, String btId, String branch) {
initBuildChain(c, btId, branch);
initHistory(c);
}
/**
* Initializes master test runs to be used as refenence.
*
* @param c Compactor.
*/
public Map<Integer, FatBuildCompacted> initHistory(IStringCompactor c) {
for (int i = 0; i < NUM_OF_TESTS_IN_MASTER; i++) {
FatBuildCompacted cache1InMaster = createFailedBuild(c, CACHE_1,
ITeamcity.DEFAULT, 500 + i, 100000 + (i * 10000))
.addTests(c, Lists.newArrayList(
createFailedTest(2L, TEST_WITH_HISTORY_FAILING_IN_MASTER),
createPassingTest(3L, TEST_WITH_HISTORY_PASSING_IN_MASTER),
createTest(50L, TEST_FLAKY_IN_MASTER, i % 2 == 0),
createPassingTest(400L, TEST_WAS_FIXED_IN_MASTER)));
if (i % 7 == 1) {
ProblemOccurrence timeout = new ProblemOccurrence();
timeout.setType(ProblemOccurrence.TC_EXECUTION_TIMEOUT);
cache1InMaster.addProblems(c, Collections.singletonList(timeout));
}
addBuildsToEmulatedStor(cache1InMaster);
}
long ageMs = TimeUnit.DAYS.toMillis(InvocationData.MAX_DAYS);
for (int i = 0; i < 134; i++) {
addBuildsToEmulatedStor(createFailedBuild(c, CACHE_1,
ITeamcity.DEFAULT, i, ageMs + (i * 10000))
.addTests(c, Lists.newArrayList(
createFailedTest(400L, TEST_WAS_FIXED_IN_MASTER))));
}
for (int i = 0; i < 10; i++) {
final FatBuildCompacted successfull =
createFatBuild(c, CACHE_1, "some-exotic-branch", i + 7777, 100020, true)
.addTests(c,
Lists.newArrayList(
createPassingTest(1L, TEST_WITHOUT_HISTORY),
createPassingTest(2L, TEST_WITH_HISTORY_FAILING_IN_MASTER),
createPassingTest(3L, TEST_WITH_HISTORY_PASSING_IN_MASTER),
createPassingTest(50L, TEST_FLAKY_IN_MASTER),
createPassingTest(400L, TEST_WAS_FIXED_IN_MASTER)));
addBuildsToEmulatedStor(successfull);
}
for (int i = 0; i < 100; i++) {
boolean failNoChanges = i == 77;
boolean failWithChanges = i == 55;
boolean passed = !failNoChanges && !failWithChanges;
FatBuildCompacted fatBuild = createFatBuild(c, CACHE_9, ITeamcity.DEFAULT, i + 9999, 1340020, passed)
.addTests(c,
Lists.newArrayList(
createTest(1L, TEST_RARE_FAILED_WITHOUT_CHANGES, !failNoChanges),
createTest(2L, TEST_RARE_FAILED_WITH_CHANGES, !failWithChanges)));
if (failWithChanges || i == 56) // add change to test status change after failure.
fatBuild.changes(new int[] {1000000 + i, 1000020 + i});
addBuildsToEmulatedStor(fatBuild);
}
return apacheBuilds();
}
/**
* Initializes
*
* @param c Compatcor.
* @param btId Build type.
* @param branch Branch tested.
*/
public void initBuildChain(IStringCompactor c, String btId, String branch) {
final FatBuildCompacted buildBuild = createFailedBuild(c, BUILD_APACHE_IGNITE, branch, 1002, 100020);
final ProblemOccurrence compile = new ProblemOccurrence();
compile.setType(ProblemOccurrence.TC_COMPILATION_ERROR);
buildBuild.addProblems(c, Collections.singletonList(compile));
final FatBuildCompacted cache1 =
createFailedBuild(c, CACHE_1, branch, 1001, 100020)
.addTests(c,
Lists.newArrayList(
createFailedTest(1L, TEST_WITHOUT_HISTORY),
createFailedTest(2L, TEST_WITH_HISTORY_FAILING_IN_MASTER),
createFailedTest(3L, TEST_WITH_HISTORY_PASSING_IN_MASTER),
createFailedTest(50L, TEST_FLAKY_IN_MASTER),
createFailedTest(400L, TEST_WAS_FIXED_IN_MASTER)));
cache1.snapshotDependencies(new int[] {buildBuild.id()});
final Build build = createJaxbBuild("CancelledBuild", branch, 1003, 100020, true);
build.status = BuildRef.STATUS_UNKNOWN;
build.state = BuildRef.STATE_FINISHED;
final FatBuildCompacted cancelledBuild = new FatBuildCompacted(c, build);
cancelledBuild.snapshotDependencies(new int[] {buildBuild.id()});
final int id = 1000;
FatBuildCompacted runAll = createFailedBuild(c, btId, branch, id, 100000)
.snapshotDependencies(new int[] {cache1.id(), cancelledBuild.id()});
addBuildsToEmulatedStor(buildBuild, cancelledBuild, cache1, runAll);
}
/**
* @param c Compactor.
* @param btId Build Type id.
* @param branch Branch.
*/
public void initChainForFlakyTest(IStringCompactor c, String btId, String branch) {
final FatBuildCompacted buildBuild = createFailedBuild(c, BUILD_APACHE_IGNITE, branch, 1002, 100020);
final FatBuildCompacted cache9 = createFailedBuild(c, CACHE_9, branch, 9001, 100090)
.addTests(c,
Lists.newArrayList(
createFailedTest(1L, TEST_RARE_FAILED_WITH_CHANGES),
createFailedTest(2L, TEST_RARE_FAILED_WITHOUT_CHANGES)));
cache9.snapshotDependencies(new int[] {buildBuild.id()});
final FatBuildCompacted chain =
createFailedBuild(c, btId, branch, 1000, 100000)
.snapshotDependencies(new int[] {cache9.id()});
addBuildsToEmulatedStor(buildBuild, cache9, chain);
}
/**
* Adds builds into emulated storage.
*
* @param builds Builds.
*/
private void addBuildsToEmulatedStor(FatBuildCompacted... builds) {
for (FatBuildCompacted build : builds) {
final FatBuildCompacted oldB = apacheBuilds.put(build.id(), build);
Preconditions.checkState(oldB == null);
}
}
@NotNull
private TestOccurrenceFull createFailedTest(long id, String name) {
return createTest(id, name, false);
}
@NotNull
private TestOccurrenceFull createPassingTest(long id, String name) {
return createTest(id, name, true);
}
@NotNull public static TestOccurrenceFull createTest(long id, String name, boolean passed) {
TestOccurrenceFull tf = new TestOccurrenceFull();
tf.test = new TestRef();
tf.test.id = String.valueOf(id);
tf.name = name;
tf.status = passed ? TestOccurrence.STATUS_SUCCESS : TestOccurrence.STATUS_FAILURE;
return tf;
}
@NotNull
public FatBuildCompacted createFailedBuild(IStringCompactor c, String btId, String branch, int id, long ageMs) {
return createFatBuild(c, btId, branch, id, ageMs, false);
}
@NotNull public static FatBuildCompacted createFatBuild(IStringCompactor c, String btId, String branch, int id,
long ageMs,
boolean passed) {
final Build build = createJaxbBuild(btId, branch, id, ageMs, passed);
return new FatBuildCompacted(c, build);
}
@NotNull
private static Build createJaxbBuild(String btId, String branch, int id, long ageMs, boolean passed) {
final Build build = new Build();
build.buildTypeId = btId;
final BuildType type = new BuildType();
type.setId(btId);
type.setName(btId);
build.setBuildType(type);
build.setId(id);
build.setStartDateTs(System.currentTimeMillis() - ageMs);
build.setBranchName(branch);
build.state = Build.STATE_FINISHED;
build.status = passed ? Build.STATUS_SUCCESS : BuildRef.STATUS_FAILURE;
return build;
}
public Map<Integer, FatBuildCompacted> apacheBuilds() {
return apacheBuilds;
}
/**
* @param c Compactor.
* @param btId Chain Build type id.
* @param branch Branch.
*/
public void initChainForTemplateDetection(IStringCompactor c, String btId, String branch) {
int firstFailedBuild = 6;
for (int i = 0; i < 10; i++) {
final FatBuildCompacted buildBuild = createFailedBuild(c, BUILD_APACHE_IGNITE, branch, 1332 + i, 100020);
final FatBuildCompacted cache8 =
createFailedBuild(c, CACHE_8, branch, 9331 + i, 100090)
.addTests(c,
Lists.newArrayList(
createTest(1L, TEST_BECAME_FAILED_IN_BRANCH, i < firstFailedBuild)))
.snapshotDependencies(new int[] {buildBuild.id()});
if (i == firstFailedBuild)
cache8.changes(new int[] {i}); //change which failed this test
final FatBuildCompacted chain =
createFailedBuild(c, btId, branch, 1220 + i, 100000)
.snapshotDependencies(new int[] {cache8.id()});
addBuildsToEmulatedStor(buildBuild, cache8, chain);
}
}
@Test
public void testTemplateDetectionInBranch() {
IStringCompactor c = injector.getInstance(IStringCompactor.class);
final String btId = "RunAll";
final String branch = "ignite-9542";
initChainForTemplateDetection(c, btId, branch);
initHistory(c);
PrChainsProcessor prcp = injector.getInstance(PrChainsProcessor.class);
final List<SuiteCurrentStatus> blockers = prcp.getBlockersSuitesStatuses(btId, branch, SRV_ID, mock(ICredentialsProv.class));
System.out.println(blockers);
Optional<TestFailure> testBecameFailed = findBlockerTestFailure(blockers, TEST_BECAME_FAILED_IN_BRANCH);
assertTrue(testBecameFailed.isPresent());
}
}