diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2015-04-22 23:32:39 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2015-04-27 15:46:23 +0200 |
commit | 2f2eea3fa99f2148171885fb85561b83a7e4e052 (patch) | |
tree | cd8ef2f2ebb6a9a75b874d5d79245396fc3aeefa /sonar-batch | |
parent | 0d5306a89afda6a6eed06df78c3f732d247a86d6 (diff) | |
download | sonarqube-2f2eea3fa99f2148171885fb85561b83a7e4e052.tar.gz sonarqube-2f2eea3fa99f2148171885fb85561b83a7e4e052.zip |
SONAR-6278 Feed tests in compute report
Diffstat (limited to 'sonar-batch')
-rw-r--r-- | sonar-batch/src/main/java/org/sonar/batch/deprecated/perspectives/BatchPerspectives.java | 7 | ||||
-rw-r--r-- | sonar-batch/src/main/java/org/sonar/batch/index/ResourceCache.java | 16 | ||||
-rw-r--r-- | sonar-batch/src/main/java/org/sonar/batch/index/ResourcePersister.java | 8 | ||||
-rw-r--r-- | sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java | 32 | ||||
-rw-r--r-- | sonar-batch/src/main/java/org/sonar/batch/report/TestExecutionAndCoveragePublisher.java | 133 | ||||
-rw-r--r-- | sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java | 1 | ||||
-rw-r--r-- | sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageTest.java) | 2 | ||||
-rw-r--r-- | sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java | 115 | ||||
-rw-r--r-- | sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/TestExecutionMediumTest.java | 105 |
9 files changed, 410 insertions, 9 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/deprecated/perspectives/BatchPerspectives.java b/sonar-batch/src/main/java/org/sonar/batch/deprecated/perspectives/BatchPerspectives.java index d743cca7301..445508ab0f5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/deprecated/perspectives/BatchPerspectives.java +++ b/sonar-batch/src/main/java/org/sonar/batch/deprecated/perspectives/BatchPerspectives.java @@ -19,10 +19,6 @@ */ package org.sonar.batch.deprecated.perspectives; -import org.sonar.core.component.PerspectiveBuilder; -import org.sonar.core.component.PerspectiveNotFoundException; -import org.sonar.core.component.ResourceComponent; - import com.google.common.collect.Maps; import org.sonar.api.batch.SonarIndex; import org.sonar.api.batch.fs.InputDir; @@ -34,6 +30,9 @@ import org.sonar.api.component.ResourcePerspectives; import org.sonar.api.resources.Directory; import org.sonar.api.resources.File; import org.sonar.api.resources.Resource; +import org.sonar.core.component.PerspectiveBuilder; +import org.sonar.core.component.PerspectiveNotFoundException; +import org.sonar.core.component.ResourceComponent; import javax.annotation.CheckForNull; diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/ResourceCache.java b/sonar-batch/src/main/java/org/sonar/batch/index/ResourceCache.java index db5c29edeb4..12ca8d6be65 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/ResourceCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/ResourceCache.java @@ -19,6 +19,7 @@ */ package org.sonar.batch.index; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Maps; @@ -27,6 +28,8 @@ import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.resources.Library; import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; +import org.sonar.core.component.ScanGraph; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -41,6 +44,16 @@ public class ResourceCache implements BatchComponent { private final Map<Library, BatchResource> libraries = Maps.newLinkedHashMap(); private BatchResource root; + private final ScanGraph scanGraph; + + public ResourceCache(ScanGraph scanGraph) { + this.scanGraph = scanGraph; + } + + @VisibleForTesting + public ResourceCache() { + this.scanGraph = null; + } @CheckForNull public BatchResource get(String componentKey) { @@ -73,6 +86,9 @@ public class ResourceCache implements BatchComponent { } else { libraries.put((Library) resource, batchResource); } + if (scanGraph != null && ResourceUtils.isPersistable(batchResource.resource())) { + scanGraph.addComponent(batchResource.resource()); + } return batchResource; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/ResourcePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/ResourcePersister.java index 3566bb0edcd..1520a672cc5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/ResourcePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/ResourcePersister.java @@ -54,15 +54,15 @@ public class ResourcePersister implements ScanPersister { private final DatabaseSession session; private final ResourcePermissions permissions; private final ResourceCache resourceCache; - private final ScanGraph graph; private final ProjectTree projectTree; + private final ScanGraph scanGraph; - public ResourcePersister(ProjectTree projectTree, DatabaseSession session, ResourcePermissions permissions, ResourceCache resourceCache, ScanGraph graph) { + public ResourcePersister(ProjectTree projectTree, DatabaseSession session, ResourcePermissions permissions, ResourceCache resourceCache, ScanGraph scanGraph) { this.projectTree = projectTree; this.session = session; this.permissions = permissions; this.resourceCache = resourceCache; - this.graph = graph; + this.scanGraph = scanGraph; } @Override @@ -101,7 +101,7 @@ public class ResourcePersister implements ScanPersister { } batchResource.setSnapshot(s); if (ResourceUtils.isPersistable(batchResource.resource())) { - graph.addComponent(batchResource.resource(), batchResource.snapshotId()); + scanGraph.completeComponent(batchResource.key(), batchResource.resource().getId(), s.getId()); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java index f2f7b379759..43cb60c251a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java @@ -264,6 +264,38 @@ public class TaskResult implements org.sonar.batch.mediumtest.ScanTaskObserver { return null; } + public BatchReport.Test testExecutionFor(InputFile testFile, String testName) { + int ref = reportComponents.get(((DefaultInputFile) testFile).key()).getRef(); + try (InputStream inputStream = FileUtils.openInputStream(getReportReader().readTests(ref))) { + BatchReport.Test test = BatchReport.Test.PARSER.parseDelimitedFrom(inputStream); + while (test != null) { + if (test.getName().equals(testName)) { + return test; + } + test = BatchReport.Test.PARSER.parseDelimitedFrom(inputStream); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return null; + } + + public BatchReport.CoverageDetail coveragePerTestFor(InputFile testFile, String testName) { + int ref = reportComponents.get(((DefaultInputFile) testFile).key()).getRef(); + try (InputStream inputStream = FileUtils.openInputStream(getReportReader().readCoverageDetails(ref))) { + BatchReport.CoverageDetail details = BatchReport.CoverageDetail.PARSER.parseDelimitedFrom(inputStream); + while (details != null) { + if (details.getTestName().equals(testName)) { + return details; + } + details = BatchReport.CoverageDetail.PARSER.parseDelimitedFrom(inputStream); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return null; + } + /** * @return null if no dependency else return dependency weight. */ diff --git a/sonar-batch/src/main/java/org/sonar/batch/report/TestExecutionAndCoveragePublisher.java b/sonar-batch/src/main/java/org/sonar/batch/report/TestExecutionAndCoveragePublisher.java new file mode 100644 index 00000000000..53c86c345a4 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/report/TestExecutionAndCoveragePublisher.java @@ -0,0 +1,133 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.test.CoverageBlock; +import org.sonar.api.test.MutableTestCase; +import org.sonar.api.test.MutableTestPlan; +import org.sonar.api.test.TestCase; +import org.sonar.batch.index.BatchResource; +import org.sonar.batch.index.ResourceCache; +import org.sonar.batch.protocol.Constants.TestStatus; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.batch.protocol.output.BatchReport.CoverageDetail; +import org.sonar.batch.protocol.output.BatchReport.Test; +import org.sonar.batch.protocol.output.BatchReportWriter; +import org.sonar.core.test.TestPlanBuilder; + +import java.util.HashSet; +import java.util.Set; + +public class TestExecutionAndCoveragePublisher implements ReportPublisherStep { + + private final class FunctionImplementation implements Function<String, CoverageDetail> { + private final MutableTestPlan testPlan; + private BatchReport.CoverageDetail.Builder builder = BatchReport.CoverageDetail.newBuilder(); + private BatchReport.CoverageDetail.CoveredFile.Builder coveredBuilder = BatchReport.CoverageDetail.CoveredFile.newBuilder(); + + private FunctionImplementation(MutableTestPlan testPlan) { + this.testPlan = testPlan; + } + + @Override + public CoverageDetail apply(String testName) { + // Take first test with provided name + MutableTestCase testCase = testPlan.testCasesByName(testName).iterator().next(); + builder.clear(); + builder.setTestName(testName); + for (CoverageBlock block : testCase.coverageBlocks()) { + coveredBuilder.clear(); + coveredBuilder.setFileRef(resourceCache.get(block.testable().component().key()).batchId()); + for (int line : block.lines()) { + coveredBuilder.addCoveredLine(line); + } + builder.addCoveredFile(coveredBuilder.build()); + } + return builder.build(); + } + } + + private final ResourceCache resourceCache; + private final TestPlanBuilder testPlanBuilder; + + public TestExecutionAndCoveragePublisher(ResourceCache resourceCache, TestPlanBuilder testPlanBuilder) { + this.resourceCache = resourceCache; + this.testPlanBuilder = testPlanBuilder; + } + + @Override + public void publish(BatchReportWriter writer) { + for (final BatchResource resource : resourceCache.all()) { + if (!resource.isFile()) { + continue; + } + + DefaultInputFile inputFile = (DefaultInputFile) resource.inputPath(); + if (inputFile.type() != Type.TEST) { + continue; + } + + final MutableTestPlan testPlan = testPlanBuilder.get(MutableTestPlan.class, inputFile.key()); + if (testPlan == null || Iterables.isEmpty(testPlan.testCases())) { + continue; + } + + final Set<String> testNamesWithCoverage = new HashSet<>(); + + writer.writeTests(resource.batchId(), Iterables.transform(testPlan.testCases(), new Function<MutableTestCase, BatchReport.Test>() { + + private BatchReport.Test.Builder builder = BatchReport.Test.newBuilder(); + + @Override + public Test apply(MutableTestCase testCase) { + builder.clear(); + builder.setName(testCase.name()); + if (testCase.doesCover()) { + testNamesWithCoverage.add(testCase.name()); + } + Long durationInMs = testCase.durationInMs(); + if (durationInMs != null) { + builder.setDurationInMs(durationInMs); + } + String msg = testCase.message(); + if (msg != null) { + builder.setMsg(msg); + } + String stack = testCase.stackTrace(); + if (stack != null) { + builder.setStacktrace(stack); + } + TestCase.Status status = testCase.status(); + if (status != null) { + builder.setStatus(TestStatus.valueOf(status.name())); + } + return builder.build(); + } + + })); + + writer.writeCoverageDetails(resource.batchId(), Iterables.transform(testNamesWithCoverage, new FunctionImplementation(testPlan))); + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index e510aae33a6..8e074792365 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -188,6 +188,7 @@ public class ProjectScanContainer extends ComponentContainer { DuplicationsPublisher.class, CoveragePublisher.class, SourcePublisher.class, + TestExecutionAndCoveragePublisher.class, ScanTaskObservers.class); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java index 6a10da86b37..b3e786841a0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java @@ -38,7 +38,7 @@ import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; -public class CoverageTest { +public class CoverageMediumTest { @org.junit.Rule public TemporaryFolder temp = new TemporaryFolder(); diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java new file mode 100644 index 00000000000..be0ba5e9207 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java @@ -0,0 +1,115 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.tests; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CoveragePerTestMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void coveragePerTestInReport() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + File testDir = new File(baseDir, "test"); + testDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "foo"); + + File xooFile2 = new File(srcDir, "sample2.xoo"); + FileUtils.write(xooFile2, "foo"); + + File xooTestFile = new File(testDir, "sampleTest.xoo"); + FileUtils.write(xooTestFile, "failure\nerror\nok\nskipped"); + + File xooTestFile2 = new File(testDir, "sample2Test.xoo"); + FileUtils.write(xooTestFile2, "test file tests"); + + File xooTestExecutionFile = new File(testDir, "sampleTest.xoo.test"); + FileUtils.write(xooTestExecutionFile, "some test:4:::OK:UNIT\n" + + "another test:10:::OK:UNIT\n" + + "test without coverage:10:::OK:UNIT\n"); + + File xooCoveragePerTestFile = new File(testDir, "sampleTest.xoo.testcoverage"); + FileUtils.write(xooCoveragePerTestFile, "some test;src/sample.xoo,10,11;src/sample2.xoo,1,2\n" + + "another test;src/sample.xoo,10,20\n"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.tests", "test") + .build()) + .start(); + + InputFile file = result.inputFile("test/sampleTest.xoo"); + org.sonar.batch.protocol.output.BatchReport.CoverageDetail someTest = result.coveragePerTestFor(file, "some test"); + assertThat(someTest.getCoveredFileList()).hasSize(2); + assertThat(someTest.getCoveredFile(0).getFileRef()).isGreaterThan(0); + assertThat(someTest.getCoveredFile(0).getCoveredLineList()).containsExactly(10, 11); + assertThat(someTest.getCoveredFile(1).getFileRef()).isGreaterThan(0); + assertThat(someTest.getCoveredFile(1).getCoveredLineList()).containsExactly(1, 2); + + org.sonar.batch.protocol.output.BatchReport.CoverageDetail anotherTest = result.coveragePerTestFor(file, "another test"); + assertThat(anotherTest.getCoveredFileList()).hasSize(1); + assertThat(anotherTest.getCoveredFile(0).getFileRef()).isGreaterThan(0); + assertThat(anotherTest.getCoveredFile(0).getCoveredLineList()).containsExactly(10, 20); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/TestExecutionMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/TestExecutionMediumTest.java new file mode 100644 index 00000000000..2e96cd42156 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/tests/TestExecutionMediumTest.java @@ -0,0 +1,105 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.tests; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.batch.protocol.Constants.TestStatus; +import org.sonar.xoo.XooPlugin; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestExecutionMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void unitTests() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + File testDir = new File(baseDir, "test"); + testDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "foo"); + + File xooTestFile = new File(testDir, "sampleTest.xoo"); + FileUtils.write(xooTestFile, "failure\nerror\nok\nskipped"); + + File xooTestExecutionFile = new File(testDir, "sampleTest.xoo.test"); + FileUtils.write(xooTestExecutionFile, "skipped::::SKIPPED:UNIT\n" + + "failure:2:Failure::FAILURE:UNIT\n" + + "error:2:Error:The stack:ERROR:UNIT\n" + + "success:4:::OK:INTEGRATION"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.tests", "test") + .build()) + .start(); + + InputFile file = result.inputFile("test/sampleTest.xoo"); + org.sonar.batch.protocol.output.BatchReport.Test success = result.testExecutionFor(file, "success"); + assertThat(success.getDurationInMs()).isEqualTo(4); + assertThat(success.getStatus()).isEqualTo(TestStatus.OK); + + org.sonar.batch.protocol.output.BatchReport.Test error = result.testExecutionFor(file, "error"); + assertThat(error.getDurationInMs()).isEqualTo(2); + assertThat(error.getStatus()).isEqualTo(TestStatus.ERROR); + assertThat(error.getMsg()).isEqualTo("Error"); + assertThat(error.getStacktrace()).isEqualTo("The stack"); + } + +} |