From: Simon Brandhof Date: Wed, 24 Feb 2016 15:19:32 +0000 (+0100) Subject: Move issue workflow from sonar-core to sonar-server X-Git-Tag: 5.5-M5~7 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=2152747f184771c70afcda6ce937eb23b072b300;p=sonarqube.git Move issue workflow from sonar-core to sonar-server --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java index 1e9b9cd90da..cc01fdf8611 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java @@ -26,7 +26,7 @@ import org.sonar.api.issue.Issue; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.IssueUpdater; -import org.sonar.core.issue.workflow.IssueWorkflow; +import org.sonar.server.issue.workflow.IssueWorkflow; import org.sonar.core.util.Uuids; import org.sonar.server.computation.analysis.AnalysisMetadataHolder; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java index 6112565f2aa..1689511d525 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java @@ -41,7 +41,7 @@ import org.sonar.core.issue.ActionPlanStats; import org.sonar.core.issue.DefaultActionPlan; import org.sonar.core.issue.DefaultIssueComment; import org.sonar.core.issue.FieldDiffs; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.Transition; import org.sonar.db.component.ResourceDao; import org.sonar.db.component.ResourceDto; import org.sonar.db.component.ResourceQuery; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java index 3d5ef020ea5..c9bee6d5f74 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java @@ -45,8 +45,8 @@ import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssueBuilder; import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.IssueUpdater; -import org.sonar.core.issue.workflow.IssueWorkflow; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.IssueWorkflow; +import org.sonar.server.issue.workflow.Transition; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java index 81b3e6cecc8..9fd9ec3500f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java @@ -28,8 +28,8 @@ import org.apache.commons.lang.StringUtils; import org.sonar.api.issue.Issue; import org.sonar.api.server.ServerSide; import org.sonar.core.issue.DefaultIssue; -import org.sonar.core.issue.workflow.IssueWorkflow; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.IssueWorkflow; +import org.sonar.server.issue.workflow.Transition; import org.sonar.server.user.UserSession; @ServerSide diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java new file mode 100644 index 00000000000..6d2f2ac7c70 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import javax.annotation.Nullable; +import org.sonar.api.issue.Issue; +import org.sonar.api.user.User; + +interface Function { + interface Context { + Issue issue(); + + Context setAssignee(@Nullable User user); + + Context setResolution(@Nullable String s); + + Context setCloseDate(boolean b); + + Context setLine(@Nullable Integer line); + } + + void execute(Context context); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java new file mode 100644 index 00000000000..9b9c88b303a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import javax.annotation.Nullable; +import org.sonar.api.issue.Issue; +import org.sonar.api.server.ServerSide; +import org.sonar.api.user.User; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; +import org.sonar.core.issue.IssueUpdater; + +@ServerSide +public class FunctionExecutor { + + private final IssueUpdater updater; + + public FunctionExecutor(IssueUpdater updater) { + this.updater = updater; + } + + public void execute(Function[] functions, DefaultIssue issue, IssueChangeContext changeContext) { + if (functions.length > 0) { + FunctionContext functionContext = new FunctionContext(updater, issue, changeContext); + for (Function function : functions) { + function.execute(functionContext); + } + } + } + + static class FunctionContext implements Function.Context { + private final IssueUpdater updater; + private final DefaultIssue issue; + private final IssueChangeContext changeContext; + + FunctionContext(IssueUpdater updater, DefaultIssue issue, IssueChangeContext changeContext) { + this.updater = updater; + this.issue = issue; + this.changeContext = changeContext; + } + + @Override + public Issue issue() { + return issue; + } + + @Override + public Function.Context setAssignee(@Nullable User user) { + updater.assign(issue, user, changeContext); + return this; + } + + @Override + public Function.Context setResolution(@Nullable String s) { + updater.setResolution(issue, s, changeContext); + return this; + } + + @Override + public Function.Context setCloseDate(boolean b) { + updater.setCloseDate(issue, b ? changeContext.date() : null, changeContext); + return this; + } + + @Override + public Function.Context setLine(@Nullable Integer line) { + updater.setLine(issue, line); + return this; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsBeingClosed.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsBeingClosed.java new file mode 100644 index 00000000000..186060ea8a4 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsBeingClosed.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.Condition; +import org.sonar.core.issue.DefaultIssue; + +enum IsBeingClosed implements Condition { + INSTANCE; + + @Override + public boolean matches(Issue issue) { + return ((DefaultIssue) issue).isBeingClosed(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManual.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManual.java new file mode 100644 index 00000000000..56336ad8e1b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManual.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.Condition; + +enum IsManual implements Condition { + INSTANCE; + + @Override + public boolean matches(Issue issue) { + return issue.ruleKey().isManual(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java new file mode 100644 index 00000000000..bb65c579d52 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java @@ -0,0 +1,212 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import java.util.List; +import org.picocontainer.Startable; +import org.sonar.api.issue.DefaultTransitions; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.HasResolution; +import org.sonar.api.issue.condition.NotCondition; +import org.sonar.api.server.ServerSide; +import org.sonar.api.web.UserRole; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; +import org.sonar.core.issue.IssueUpdater; + +@ServerSide +public class IssueWorkflow implements Startable { + + private final FunctionExecutor functionExecutor; + private final IssueUpdater updater; + private StateMachine machine; + + public IssueWorkflow(FunctionExecutor functionExecutor, IssueUpdater updater) { + this.functionExecutor = functionExecutor; + this.updater = updater; + } + + @Override + public void start() { + StateMachine.Builder builder = StateMachine.builder() + // order is important for UI + .states(Issue.STATUS_OPEN, Issue.STATUS_CONFIRMED, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED, Issue.STATUS_CLOSED); + + buildManualTransitions(builder); + buildAutomaticTransitions(builder); + machine = builder.build(); + } + + private void buildManualTransitions(StateMachine.Builder builder) { + builder.transition(Transition.builder(DefaultTransitions.CONFIRM) + .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED) + .functions(new SetResolution(null)) + .build()) + .transition(Transition.builder(DefaultTransitions.CONFIRM) + .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CONFIRMED) + .functions(new SetResolution(null)) + .build()) + .transition(Transition.builder(DefaultTransitions.UNCONFIRM) + .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_REOPENED) + .functions(new SetResolution(null)) + .build()) + .transition(Transition.builder(DefaultTransitions.RESOLVE) + .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) + .functions(new SetResolution(Issue.RESOLUTION_FIXED)) + .build()) + .transition(Transition.builder(DefaultTransitions.RESOLVE) + .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) + .functions(new SetResolution(Issue.RESOLUTION_FIXED)) + .build()) + .transition(Transition.builder(DefaultTransitions.RESOLVE) + .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED) + .functions(new SetResolution(Issue.RESOLUTION_FIXED)) + .build()) + .transition(Transition.builder(DefaultTransitions.REOPEN) + .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED) + .functions(new SetResolution(null)) + .build()) + .transition(Transition.builder(DefaultTransitions.REOPEN) + .conditions(IsManual.INSTANCE) + .from(Issue.STATUS_CLOSED).to(Issue.STATUS_REOPENED) + .functions(new SetResolution(null), new SetCloseDate(false)) + .build()) + + // resolve as false-positive + .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) + .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) + .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) + .build()) + .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) + .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) + .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) + .build()) + .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) + .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED) + .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) + .build()) + + // resolve as won't fix + .transition(Transition.builder(DefaultTransitions.WONT_FIX) + .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) + .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) + .build()) + .transition(Transition.builder(DefaultTransitions.WONT_FIX) + .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) + .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) + .build()) + .transition(Transition.builder(DefaultTransitions.WONT_FIX) + .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED) + .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) + .build()); + + } + + private void buildAutomaticTransitions(StateMachine.Builder builder) { + // Close the "end of life" issues (disabled/deleted rule, deleted component) + builder + .transition(Transition.builder("automaticclose") + .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED) + .conditions(IsBeingClosed.INSTANCE) + .functions(SetClosed.INSTANCE, new SetCloseDate(true)) + .automatic() + .build()) + .transition(Transition.builder("automaticclose") + .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED) + .conditions(IsBeingClosed.INSTANCE) + .functions(SetClosed.INSTANCE, new SetCloseDate(true)) + .automatic() + .build()) + .transition(Transition.builder("automaticclose") + .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_CLOSED) + .conditions(IsBeingClosed.INSTANCE) + .functions(SetClosed.INSTANCE, new SetCloseDate(true)) + .automatic() + .build()) + .transition(Transition.builder("automaticclose") + .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED) + .conditions(new OrCondition(IsBeingClosed.INSTANCE, IsManual.INSTANCE)) + .functions(SetClosed.INSTANCE, new SetCloseDate(true)) + .automatic() + .build()) + + // Reopen issues that are marked as resolved but that are still alive. + // Manual issues are kept resolved. + .transition(Transition.builder("automaticreopen") + .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED) + .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED), new NotCondition(IsManual.INSTANCE)) + .functions(new SetResolution(null), new SetCloseDate(false)) + .automatic() + .build()); + } + + @Override + public void stop() { + // nothing to do + } + + public boolean doTransition(DefaultIssue issue, String transitionKey, IssueChangeContext issueChangeContext) { + Transition transition = stateOf(issue).transition(transitionKey); + if (transition != null && !transition.automatic()) { + functionExecutor.execute(transition.functions(), issue, issueChangeContext); + updater.setStatus(issue, transition.to(), issueChangeContext); + return true; + } + return false; + } + + public List outTransitions(Issue issue) { + State state = machine.state(issue.status()); + if (state == null) { + throw new IllegalArgumentException("Unknown status: " + issue.status()); + } + return state.outManualTransitions(issue); + } + + public void doAutomaticTransition(DefaultIssue issue, IssueChangeContext issueChangeContext) { + Transition transition = stateOf(issue).outAutomaticTransition(issue); + if (transition != null) { + functionExecutor.execute(transition.functions(), issue, issueChangeContext); + updater.setStatus(issue, transition.to(), issueChangeContext); + } + } + + public List statusKeys() { + return machine.stateKeys(); + } + + private State stateOf(DefaultIssue issue) { + State state = machine.state(issue.status()); + if (state == null) { + throw new IllegalStateException("Unknown status: " + issue.status() + " [issue=" + issue.key() + "]"); + } + return state; + } + + StateMachine machine() { + return machine; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/OrCondition.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/OrCondition.java new file mode 100644 index 00000000000..9eb9239dee5 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/OrCondition.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.Condition; + +public class OrCondition implements Condition { + private final Condition[] conditions; + + public OrCondition(Condition... conditions) { + this.conditions = conditions; + } + + @Override + public boolean matches(Issue issue) { + for (Condition condition : conditions) { + if (condition.matches(issue)) { + return true; + } + } + return false; + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetCloseDate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetCloseDate.java new file mode 100644 index 00000000000..c5d42162b39 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetCloseDate.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +class SetCloseDate implements Function { + private final boolean set; + + public SetCloseDate(boolean set) { + this.set = set; + } + + @Override + public void execute(Context context) { + context.setCloseDate(set); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetClosed.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetClosed.java new file mode 100644 index 00000000000..cb2b9f2471c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetClosed.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.sonar.api.issue.Issue; +import org.sonar.core.issue.DefaultIssue; + +public enum SetClosed implements Function { + INSTANCE; + + @Override + public void execute(Context context) { + DefaultIssue issue = (DefaultIssue) context.issue(); + if (issue.isOnDisabledRule()) { + context.setResolution(Issue.RESOLUTION_REMOVED); + } else { + context.setResolution(Issue.RESOLUTION_FIXED); + } + + // closed issues are not "tracked" -> the line number does not evolve anymore + // when code changes. That's misleading for end-users, so line number + // is unset. + context.setLine(null); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetResolution.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetResolution.java new file mode 100644 index 00000000000..b0a5992b635 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetResolution.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import javax.annotation.Nullable; + +public class SetResolution implements Function { + private final String resolution; + + public SetResolution(@Nullable String resolution) { + this.resolution = resolution; + } + + @Override + public void execute(Context context) { + context.setResolution(resolution); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/State.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/State.java new file mode 100644 index 00000000000..60510a4b77f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/State.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import java.util.List; +import java.util.Set; +import javax.annotation.CheckForNull; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.issue.Issue; + +public class State { + private final String key; + private final Transition[] outTransitions; + + public State(String key, Transition[] outTransitions) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "State key must be set"); + Preconditions.checkArgument(StringUtils.isAllUpperCase(key), "State key must be upper-case"); + checkDuplications(outTransitions, key); + + this.key = key; + this.outTransitions = outTransitions; + } + + private static void checkDuplications(Transition[] transitions, String stateKey) { + Set keys = Sets.newHashSet(); + for (Transition transition : transitions) { + if (keys.contains(transition.key())) { + throw new IllegalArgumentException("Transition '" + transition.key() + + "' is declared several times from the originating state '" + stateKey + "'"); + } + keys.add(transition.key()); + } + } + + public List outManualTransitions(Issue issue) { + List result = Lists.newArrayList(); + for (Transition transition : outTransitions) { + if (!transition.automatic() && transition.supports(issue)) { + result.add(transition); + } + } + return result; + } + + @CheckForNull + public Transition outAutomaticTransition(Issue issue) { + Transition result = null; + for (Transition transition : outTransitions) { + if (transition.automatic() && transition.supports(issue)) { + if (result == null) { + result = transition; + } else { + throw new IllegalStateException("Several automatic transitions are available for issue: " + issue); + } + } + } + return result; + } + + Transition transition(String transitionKey) { + for (Transition transition : outTransitions) { + if (transitionKey.equals(transition.key())) { + return transition; + } + } + throw new IllegalStateException("Transition from state " + key + " does not exist: " + transitionKey); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/StateMachine.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/StateMachine.java new file mode 100644 index 00000000000..f643ec96dbe --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/StateMachine.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Sets; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.CheckForNull; + +public class StateMachine { + + private final List keys; + private final Map byKey; + + private StateMachine(Builder builder) { + this.keys = ImmutableList.copyOf(builder.states); + ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); + for (String stateKey : builder.states) { + List outTransitions = builder.outTransitions.get(stateKey); + State state = new State(stateKey, outTransitions.toArray(new Transition[outTransitions.size()])); + mapBuilder.put(stateKey, state); + } + byKey = mapBuilder.build(); + } + + @CheckForNull + public State state(String stateKey) { + return byKey.get(stateKey); + } + + public List stateKeys() { + return keys; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final Set states = Sets.newLinkedHashSet(); + // transitions per originating state + private final ListMultimap outTransitions = ArrayListMultimap.create(); + + private Builder() { + } + + public Builder states(String... keys) { + states.addAll(Arrays.asList(keys)); + return this; + } + + public Builder transition(Transition transition) { + Preconditions.checkArgument(states.contains(transition.from()), "Originating state does not exist: " + transition.from()); + Preconditions.checkArgument(states.contains(transition.to()), "Destination state does not exist: " + transition.to()); + outTransitions.put(transition.from(), transition); + return this; + } + + public StateMachine build() { + Preconditions.checkArgument(!states.isEmpty(), "At least one state is required"); + return new StateMachine(this); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Transition.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Transition.java new file mode 100644 index 00000000000..a0659390c93 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Transition.java @@ -0,0 +1,177 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.List; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.Condition; + +public class Transition { + private final String key; + private final String from; + private final String to; + private final Condition[] conditions; + private final Function[] functions; + private final boolean automatic; + private String requiredProjectPermission; + + private Transition(TransitionBuilder builder) { + key = builder.key; + from = builder.from; + to = builder.to; + conditions = builder.conditions.toArray(new Condition[builder.conditions.size()]); + functions = builder.functions.toArray(new Function[builder.functions.size()]); + automatic = builder.automatic; + requiredProjectPermission = builder.requiredProjectPermission; + } + + public String key() { + return key; + } + + String from() { + return from; + } + + String to() { + return to; + } + + Condition[] conditions() { + return conditions; + } + + Function[] functions() { + return functions; + } + + boolean automatic() { + return automatic; + } + + public boolean supports(Issue issue) { + for (Condition condition : conditions) { + if (!condition.matches(issue)) { + return false; + } + } + return true; + } + + public String requiredProjectPermission() { + return requiredProjectPermission; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Transition that = (Transition) o; + if (!from.equals(that.from)) { + return false; + } + if (!key.equals(that.key)) { + return false; + } + return to.equals(that.to); + } + + @Override + public int hashCode() { + int result = key.hashCode(); + result = 31 * result + from.hashCode(); + result = 31 * result + to.hashCode(); + return result; + } + + @Override + public String toString() { + return String.format("%s->%s->%s", from, key, to); + } + + public static Transition create(String key, String from, String to) { + return builder(key).from(from).to(to).build(); + } + + public static TransitionBuilder builder(String key) { + return new TransitionBuilder(key); + } + + public static class TransitionBuilder { + private final String key; + private String from; + private String to; + private List conditions = Lists.newArrayList(); + private List functions = Lists.newArrayList(); + private boolean automatic = false; + private String requiredProjectPermission; + + private TransitionBuilder(String key) { + this.key = key; + } + + public TransitionBuilder from(String from) { + this.from = from; + return this; + } + + public TransitionBuilder to(String to) { + this.to = to; + return this; + } + + public TransitionBuilder conditions(Condition... c) { + this.conditions.addAll(Arrays.asList(c)); + return this; + } + + public TransitionBuilder functions(Function... f) { + this.functions.addAll(Arrays.asList(f)); + return this; + } + + public TransitionBuilder automatic() { + this.automatic = true; + return this; + } + + public TransitionBuilder requiredProjectPermission(String requiredProjectPermission) { + this.requiredProjectPermission = requiredProjectPermission; + return this; + } + + public Transition build() { + Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "Transition key must be set"); + Preconditions.checkArgument(StringUtils.isAllLowerCase(key), "Transition key must be lower-case"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(from), "Originating status must be set"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(to), "Destination status must be set"); + return new Transition(this); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/UnsetAssignee.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/UnsetAssignee.java new file mode 100644 index 00000000000..8ac7cd59aee --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/UnsetAssignee.java @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +enum UnsetAssignee implements Function { + INSTANCE; + + @Override + public void execute(Context context) { + context.setAssignee(null); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/package-info.java new file mode 100644 index 00000000000..cf6d81f4186 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.issue.workflow; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java index 7c6e1dcf4c7..6de68b3d926 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java @@ -22,7 +22,7 @@ package org.sonar.server.issue.ws; import org.sonar.api.issue.Issue; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.text.JsonWriter; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.Transition; import org.sonar.server.issue.ActionService; import org.sonar.server.issue.IssueService; import org.sonar.server.user.UserSession; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java index 76597e92797..a24ccaad198 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java @@ -29,7 +29,7 @@ import java.util.Map; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.Transition; import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.ActionPlanDto; import org.sonar.db.issue.IssueChangeDto; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java index 8bc24de9622..246fe4eb118 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java @@ -35,7 +35,7 @@ import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.Duration; import org.sonar.api.utils.Durations; import org.sonar.api.utils.Paging; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.Transition; import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.ActionPlanDto; import org.sonar.db.issue.IssueChangeDto; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 02fb6cd3345..9236429f5ba 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -32,8 +32,8 @@ import org.sonar.api.rules.XMLRuleParser; import org.sonar.api.server.rule.RulesDefinitionXmlLoader; import org.sonar.core.component.DefaultResourceTypes; import org.sonar.core.issue.IssueUpdater; -import org.sonar.core.issue.workflow.FunctionExecutor; -import org.sonar.core.issue.workflow.IssueWorkflow; +import org.sonar.server.issue.workflow.FunctionExecutor; +import org.sonar.server.issue.workflow.IssueWorkflow; import org.sonar.core.timemachine.Periods; import org.sonar.core.user.DefaultUserFinder; import org.sonar.core.user.DeprecatedUserFinder; diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueLifecycleTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueLifecycleTest.java index 19eb16134df..844d0ff8bbf 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueLifecycleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueLifecycleTest.java @@ -26,7 +26,7 @@ import org.sonar.api.utils.Duration; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.IssueUpdater; -import org.sonar.core.issue.workflow.IssueWorkflow; +import org.sonar.server.issue.workflow.IssueWorkflow; import org.sonar.db.protobuf.DbCommons; import org.sonar.db.protobuf.DbIssues; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java index b0c47aa7ea0..4544480729c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java @@ -36,7 +36,7 @@ import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; import org.sonar.api.security.DefaultGroups; import org.sonar.api.web.UserRole; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.Transition; import org.sonar.core.permission.GlobalPermissions; import org.sonar.db.DbClient; import org.sonar.db.DbSession; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java index 087531cff55..2ea9787433b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java @@ -27,8 +27,8 @@ import org.junit.Test; import org.sonar.api.issue.Issue; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.IssueChangeContext; -import org.sonar.core.issue.workflow.IssueWorkflow; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.IssueWorkflow; +import org.sonar.server.issue.workflow.Transition; import org.sonar.server.tester.UserSessionRule; import static com.google.common.collect.Lists.newArrayList; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsBeingClosedTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsBeingClosedTest.java new file mode 100644 index 00000000000..62ebe571d57 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsBeingClosedTest.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; +import org.sonar.core.issue.DefaultIssue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.server.issue.workflow.IsBeingClosed.INSTANCE; + +public class IsBeingClosedTest { + + @Test + public void should_be_end_of_life() { + DefaultIssue issue = new DefaultIssue(); + assertThat(INSTANCE.matches(issue.setBeingClosed(true))).isTrue(); + assertThat(INSTANCE.matches(issue.setBeingClosed(false))).isFalse(); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsManualTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsManualTest.java new file mode 100644 index 00000000000..c8458031b03 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsManualTest.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.core.issue.DefaultIssue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.server.issue.workflow.IsManual.INSTANCE; + +public class IsManualTest { + + @Test + public void should_match() { + DefaultIssue issue = new DefaultIssue(); + assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "R1")))).isTrue(); + assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of("java", "R1")))).isFalse(); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java new file mode 100644 index 00000000000..1900c2c0ce1 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java @@ -0,0 +1,432 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import javax.annotation.Nullable; +import org.apache.commons.lang.time.DateUtils; +import org.junit.Test; +import org.sonar.api.issue.DefaultTransitions; +import org.sonar.api.rule.RuleKey; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; +import org.sonar.core.issue.IssueUpdater; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; +import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; +import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX; +import static org.sonar.api.issue.Issue.STATUS_CLOSED; +import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; +import static org.sonar.api.issue.Issue.STATUS_OPEN; +import static org.sonar.api.issue.Issue.STATUS_REOPENED; +import static org.sonar.api.issue.Issue.STATUS_RESOLVED; +import static org.sonar.api.rule.RuleKey.MANUAL_REPOSITORY_KEY; + +public class IssueWorkflowTest { + + IssueUpdater updater = new IssueUpdater(); + IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater); + + @Test + public void init_state_machine() { + assertThat(workflow.machine()).isNull(); + workflow.start(); + assertThat(workflow.machine()).isNotNull(); + assertThat(workflow.machine().state(STATUS_OPEN)).isNotNull(); + assertThat(workflow.machine().state(STATUS_CONFIRMED)).isNotNull(); + assertThat(workflow.machine().state(STATUS_CLOSED)).isNotNull(); + assertThat(workflow.machine().state(STATUS_REOPENED)).isNotNull(); + assertThat(workflow.machine().state(STATUS_RESOLVED)).isNotNull(); + workflow.stop(); + } + + @Test + public void list_statuses() { + workflow.start(); + // order is important for UI + assertThat(workflow.statusKeys()).containsSequence(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED); + } + + @Test + public void list_out_transitions_from_status_open() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue().setStatus(STATUS_OPEN); + List transitions = workflow.outTransitions(issue); + assertThat(keys(transitions)).containsOnly("confirm", "falsepositive", "resolve", "wontfix"); + } + + @Test + public void list_out_transitions_from_status_confirmed() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CONFIRMED); + List transitions = workflow.outTransitions(issue); + assertThat(keys(transitions)).containsOnly("unconfirm", "falsepositive", "resolve", "wontfix"); + } + + @Test + public void list_out_transitions_from_status_resolved() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue().setStatus(STATUS_RESOLVED); + List transitions = workflow.outTransitions(issue); + assertThat(keys(transitions)).containsOnly("reopen"); + } + + @Test + public void list_out_transitions_from_status_reopen() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue().setStatus(STATUS_REOPENED); + List transitions = workflow.outTransitions(issue); + assertThat(keys(transitions)).containsOnly("confirm", "resolve", "falsepositive", "wontfix"); + } + + @Test + public void list_no_out_transition_from_status_closed() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CLOSED).setRuleKey(RuleKey.of("java", "R1 ")); + List transitions = workflow.outTransitions(issue); + assertThat(transitions).isEmpty(); + } + + @Test + public void list_out_transitions_from_status_closed_on_manual_issue() { + workflow.start(); + + // Manual issue because of reporter + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setStatus(STATUS_CLOSED) + .setRuleKey(RuleKey.of("manual", "Performance")) + .setReporter("simon"); + + List transitions = workflow.outTransitions(issue); + assertThat(keys(transitions)).containsOnly("reopen"); + } + + @Test + public void fail_if_unknown_status_when_listing_transitions() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue().setStatus("xxx"); + try { + workflow.outTransitions(issue); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Unknown status: xxx"); + } + } + + @Test + public void automatically_close_resolved_issue() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setRuleKey(RuleKey.of("js", "S001")) + .setResolution(RESOLUTION_FIXED) + .setStatus(STATUS_RESOLVED) + .setNew(false) + .setBeingClosed(true); + Date now = new Date(); + workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); + assertThat(issue.status()).isEqualTo(STATUS_CLOSED); + assertThat(issue.closeDate()).isNotNull(); + assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND)); + } + + @Test + public void close_open_dead_issue() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setResolution(null) + .setStatus(STATUS_OPEN) + .setNew(false) + .setBeingClosed(true); + Date now = new Date(); + workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); + assertThat(issue.status()).isEqualTo(STATUS_CLOSED); + assertThat(issue.closeDate()).isNotNull(); + assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND)); + } + + @Test + public void close_reopened_dead_issue() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setResolution(null) + .setStatus(STATUS_REOPENED) + .setNew(false) + .setBeingClosed(true); + Date now = new Date(); + workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); + assertThat(issue.status()).isEqualTo(STATUS_CLOSED); + assertThat(issue.closeDate()).isNotNull(); + assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND)); + } + + @Test + public void close_confirmed_dead_issue() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setResolution(null) + .setStatus(STATUS_CONFIRMED) + .setNew(false) + .setBeingClosed(true); + Date now = new Date(); + workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); + assertThat(issue.status()).isEqualTo(STATUS_CLOSED); + assertThat(issue.closeDate()).isNotNull(); + assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND)); + } + + @Test + public void fail_if_unknown_status_on_automatic_trans() { + workflow.start(); + + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setResolution(RESOLUTION_FIXED) + .setStatus("xxx") + .setNew(false) + .setBeingClosed(true); + try { + workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date())); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Unknown status: xxx [issue=ABCDE]"); + } + } + + @Test + public void flag_as_false_positive() { + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setStatus(STATUS_OPEN) + .setRuleKey(RuleKey.of("squid", "AvoidCycle")) + .setAssignee("morgan"); + + workflow.start(); + workflow.doTransition(issue, DefaultTransitions.FALSE_POSITIVE, IssueChangeContext.createScan(new Date())); + + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FALSE_POSITIVE); + assertThat(issue.status()).isEqualTo(STATUS_RESOLVED); + + // should remove assignee + assertThat(issue.assignee()).isNull(); + } + + @Test + public void wont_fix() { + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setStatus(STATUS_OPEN) + .setRuleKey(RuleKey.of("squid", "AvoidCycle")) + .setAssignee("morgan"); + + workflow.start(); + workflow.doTransition(issue, DefaultTransitions.WONT_FIX, IssueChangeContext.createScan(new Date())); + + assertThat(issue.resolution()).isEqualTo(RESOLUTION_WONT_FIX); + assertThat(issue.status()).isEqualTo(STATUS_RESOLVED); + + // should remove assignee + assertThat(issue.assignee()).isNull(); + } + + /** + * User marks the manual issue as resolved -> issue is automatically + * closed. + */ + @Test + public void automatically_close_resolved_manual_issue() { + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setStatus(STATUS_OPEN) + .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance")); + + workflow.start(); + + assertThat(workflow.outTransitions(issue)).containsOnly( + Transition.create("confirm", "OPEN", "CONFIRMED"), + Transition.create("resolve", "OPEN", "RESOLVED"), + Transition.create("falsepositive", "OPEN", "RESOLVED"), + Transition.create("wontfix", "OPEN", "RESOLVED")); + + workflow.doTransition(issue, "resolve", mock(IssueChangeContext.class)); + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); + assertThat(issue.status()).isEqualTo("RESOLVED"); + + assertThat(workflow.outTransitions(issue)).containsOnly( + Transition.create("reopen", "RESOLVED", "REOPENED")); + + workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); + assertThat(issue.status()).isEqualTo(STATUS_CLOSED); + } + + /** + * Manual issue is fixed because the file does not exist anymore + * or the tracking engine did not find the associated code + * -> the issue is closed + */ + @Test + public void automatically_close_manual_issue_on_deleted_code() { + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setStatus(STATUS_OPEN) + .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance")) + .setBeingClosed(true); + + workflow.start(); + + workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); + assertThat(issue.status()).isEqualTo(STATUS_CLOSED); + } + + /** + * Corner-case : the manual issue was marked as resolved by user but at the same + * time the file or the associated line was deleted. + */ + @Test + public void automatically_close_resolved_manual_issue_on_deleted_code() { + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance")) + + // resolved by user + .setResolution(RESOLUTION_FIXED) + .setStatus(STATUS_RESOLVED) + + // but unmatched by tracking engine + .setBeingClosed(true); + + workflow.start(); + + workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); + assertThat(issue.status()).isEqualTo(STATUS_CLOSED); + } + + @Test + public void manual_issues_be_confirmed_then_kept_open() { + // Manual issue because of reporter + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setStatus(STATUS_OPEN) + .setRuleKey(RuleKey.of("manual", "Performance")) + .setReporter("simon"); + + workflow.start(); + + assertThat(workflow.outTransitions(issue)).containsOnly( + Transition.create("confirm", "OPEN", "CONFIRMED"), + Transition.create("resolve", "OPEN", "RESOLVED"), + Transition.create("falsepositive", "OPEN", "RESOLVED"), + Transition.create("wontfix", "OPEN", "RESOLVED")); + + workflow.doTransition(issue, "confirm", mock(IssueChangeContext.class)); + assertThat(issue.resolution()).isNull(); + assertThat(issue.status()).isEqualTo("CONFIRMED"); + + assertThat(workflow.outTransitions(issue)).containsOnly( + Transition.create("unconfirm", "CONFIRMED", "REOPENED"), + Transition.create("resolve", "CONFIRMED", "RESOLVED"), + Transition.create("falsepositive", "CONFIRMED", "RESOLVED"), + Transition.create("wontfix", "CONFIRMED", "RESOLVED")); + + // keep confirmed and unresolved + workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); + assertThat(issue.resolution()).isNull(); + assertThat(issue.status()).isEqualTo("CONFIRMED"); + + // unconfirm + workflow.doTransition(issue, "unconfirm", mock(IssueChangeContext.class)); + assertThat(issue.resolution()).isNull(); + assertThat(issue.status()).isEqualTo("REOPENED"); + } + + @Test + public void manual_issue_on_removed_rule_be_closed() { + // Manual issue because of reporter + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setStatus(STATUS_OPEN) + .setRuleKey(RuleKey.of("manual", "Performance")) + .setReporter("simon") + .setBeingClosed(true) + .setOnDisabledRule(true); + + workflow.start(); + + workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); + assertThat(issue.resolution()).isEqualTo("REMOVED"); + assertThat(issue.status()).isEqualTo(STATUS_CLOSED); + } + + @Test + public void manual_issue_on_removed_component_be_closed() { + // Manual issue because of reporter + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setStatus(STATUS_OPEN) + .setRuleKey(RuleKey.of("manual", "Performance")) + .setReporter("simon") + .setBeingClosed(true) + .setOnDisabledRule(false); + + workflow.start(); + + workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); + assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); + assertThat(issue.status()).isEqualTo(STATUS_CLOSED); + } + + private Collection keys(List transitions) { + return Collections2.transform(transitions, new Function() { + @Override + public String apply(@Nullable Transition transition) { + return transition.key(); + } + }); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/OrConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/OrConditionTest.java new file mode 100644 index 00000000000..7b72a1a3741 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/OrConditionTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.Condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class OrConditionTest { + + private static final Condition TRUE_CONDITION = new BooleanCondition(true); + private static final Condition FALSE_CONDITION = new BooleanCondition(false); + Issue issue = mock(Issue.class); + + @Test + public void match() { + assertThat(new OrCondition(TRUE_CONDITION).matches(issue)).isTrue(); + assertThat(new OrCondition(FALSE_CONDITION).matches(issue)).isFalse(); + assertThat(new OrCondition(FALSE_CONDITION, TRUE_CONDITION).matches(issue)).isTrue(); + assertThat(new OrCondition(FALSE_CONDITION, FALSE_CONDITION).matches(issue)).isFalse(); + } + + private static class BooleanCondition implements Condition { + private final boolean b; + + public BooleanCondition(boolean b) { + this.b = b; + } + + @Override + public boolean matches(Issue issue) { + return b; + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetCloseDateTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetCloseDateTest.java new file mode 100644 index 00000000000..09ab2d4f525 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetCloseDateTest.java @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class SetCloseDateTest { + @Test + public void should_set_close_date() { + SetCloseDate function = new SetCloseDate(true); + Function.Context context = mock(Function.Context.class); + function.execute(context); + verify(context, times(1)).setCloseDate(true); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetClosedTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetClosedTest.java new file mode 100644 index 00000000000..3638ebd0ad5 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetClosedTest.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; +import org.sonar.api.issue.Issue; +import org.sonar.core.issue.DefaultIssue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.sonar.server.issue.workflow.SetClosed.INSTANCE; + +public class SetClosedTest { + + Function.Context context = mock(Function.Context.class); + + @Test + public void should_resolve_as_fixed() { + Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(false); + when(context.issue()).thenReturn(issue); + INSTANCE.execute(context); + verify(context, times(1)).setResolution(Issue.RESOLUTION_FIXED); + } + + @Test + public void should_resolve_as_removed_when_rule_is_disabled() { + Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(true); + when(context.issue()).thenReturn(issue); + INSTANCE.execute(context); + verify(context, times(1)).setResolution(Issue.RESOLUTION_REMOVED); + } + + @Test + public void line_number_must_be_unset() { + Issue issue = new DefaultIssue().setBeingClosed(true).setLine(10); + when(context.issue()).thenReturn(issue); + INSTANCE.execute(context); + verify(context).setLine(null); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetResolutionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetResolutionTest.java new file mode 100644 index 00000000000..ed397d30a0c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetResolutionTest.java @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class SetResolutionTest { + @Test + public void execute() { + SetResolution function = new SetResolution("FIXED"); + Function.Context context = mock(Function.Context.class); + function.execute(context); + verify(context, times(1)).setResolution("FIXED"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateMachineTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateMachineTest.java new file mode 100644 index 00000000000..222a7d491e5 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateMachineTest.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class StateMachineTest { + @Test + public void keep_order_of_state_keys() { + StateMachine machine = StateMachine.builder().states("OPEN", "RESOLVED", "CLOSED").build(); + + assertThat(machine.stateKeys()).containsSequence("OPEN", "RESOLVED", "CLOSED"); + } + + @Test + public void stateKey() { + StateMachine machine = StateMachine.builder() + .states("OPEN", "RESOLVED", "CLOSED") + .transition(Transition.builder("resolve").from("OPEN").to("RESOLVED").build()) + .build(); + + assertThat(machine.state("OPEN")).isNotNull(); + assertThat(machine.state("OPEN").transition("resolve")).isNotNull(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateTest.java new file mode 100644 index 00000000000..7677c3e3954 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateTest.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class StateTest { + + Transition t1 = Transition.builder("close").from("OPEN").to("CLOSED").build(); + + @Test + public void key_should_be_set() { + try { + new State("", new Transition[0]); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("State key must be set"); + } + } + + @Test + public void key_should_be_upper_case() { + try { + new State("close", new Transition[0]); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("State key must be upper-case"); + } + } + + @Test + public void no_duplicated_out_transitions() { + try { + new State("CLOSE", new Transition[] {t1, t1}); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Transition 'close' is declared several times from the originating state 'CLOSE'"); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/TransitionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/TransitionTest.java new file mode 100644 index 00000000000..a8535a0c09c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/TransitionTest.java @@ -0,0 +1,149 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; +import org.sonar.api.issue.condition.Condition; +import org.sonar.core.issue.DefaultIssue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TransitionTest { + + Condition condition1 = mock(Condition.class); + Condition condition2 = mock(Condition.class); + Function function1 = mock(Function.class); + Function function2 = mock(Function.class); + + @Test + public void test_builder() throws Exception { + Transition transition = Transition.builder("close") + .from("OPEN").to("CLOSED") + .conditions(condition1, condition2) + .functions(function1, function2) + .build(); + assertThat(transition.key()).isEqualTo("close"); + assertThat(transition.from()).isEqualTo("OPEN"); + assertThat(transition.to()).isEqualTo("CLOSED"); + assertThat(transition.conditions()).containsOnly(condition1, condition2); + assertThat(transition.functions()).containsOnly(function1, function2); + assertThat(transition.automatic()).isFalse(); + } + + @Test + public void test_simplest_transition() throws Exception { + Transition transition = Transition.builder("close") + .from("OPEN").to("CLOSED") + .build(); + assertThat(transition.key()).isEqualTo("close"); + assertThat(transition.from()).isEqualTo("OPEN"); + assertThat(transition.to()).isEqualTo("CLOSED"); + assertThat(transition.conditions()).isEmpty(); + assertThat(transition.functions()).isEmpty(); + } + + @Test + public void key_should_be_set() { + try { + Transition.builder("").from("OPEN").to("CLOSED").build(); + fail(); + } catch (Exception e) { + assertThat(e).hasMessage("Transition key must be set"); + } + } + + @Test + public void key_should_be_lower_case() { + try { + Transition.builder("CLOSE").from("OPEN").to("CLOSED").build(); + fail(); + } catch (Exception e) { + assertThat(e).hasMessage("Transition key must be lower-case"); + } + } + + @Test + public void originating_status_should_be_set() { + try { + Transition.builder("close").from("").to("CLOSED").build(); + fail(); + } catch (Exception e) { + assertThat(e).hasMessage("Originating status must be set"); + } + } + + @Test + public void destination_status_should_be_set() { + try { + Transition.builder("close").from("OPEN").to("").build(); + fail(); + } catch (Exception e) { + assertThat(e).hasMessage("Destination status must be set"); + } + } + + @Test + public void should_verify_conditions() { + DefaultIssue issue = new DefaultIssue(); + Transition transition = Transition.builder("close") + .from("OPEN").to("CLOSED") + .conditions(condition1, condition2) + .build(); + + when(condition1.matches(issue)).thenReturn(true); + when(condition2.matches(issue)).thenReturn(false); + assertThat(transition.supports(issue)).isFalse(); + + when(condition1.matches(issue)).thenReturn(true); + when(condition2.matches(issue)).thenReturn(true); + assertThat(transition.supports(issue)).isTrue(); + } + + @Test + public void test_equals_and_hashCode() throws Exception { + Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED"); + Transition t2 = Transition.create("resolve", "REOPENED", "RESOLVED"); + Transition t3 = Transition.create("confirm", "OPEN", "CONFIRMED"); + + assertThat(t1).isNotEqualTo(t2); + assertThat(t1).isNotEqualTo(t3); + assertThat(t1).isEqualTo(t1); + + assertThat(t1.hashCode()).isEqualTo(t1.hashCode()); + } + + @Test + public void test_toString() throws Exception { + Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED"); + assertThat(t1.toString()).isEqualTo("OPEN->resolve->RESOLVED"); + } + + @Test + public void test_automatic_transition() throws Exception { + Transition transition = Transition.builder("close") + .from("OPEN").to("CLOSED") + .automatic() + .build(); + assertThat(transition.automatic()).isTrue(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/UnsetAssigneeTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/UnsetAssigneeTest.java new file mode 100644 index 00000000000..8ed5c835a87 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/UnsetAssigneeTest.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.server.issue.workflow; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.sonar.server.issue.workflow.UnsetAssignee.INSTANCE; + +public class UnsetAssigneeTest { + + @Test + public void unassign() { + Function.Context context = mock(Function.Context.class); + INSTANCE.execute(context); + verify(context, times(1)).setAssignee(null); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java index 1aeb60521bc..8db1776c588 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java @@ -30,7 +30,7 @@ import org.sonar.api.issue.Issue; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.text.JsonWriter; import org.sonar.core.issue.DefaultIssue; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.Transition; import org.sonar.server.issue.ActionService; import org.sonar.server.issue.IssueService; import org.sonar.server.tester.UserSessionRule; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java index 973defab5c9..67ad2c679d4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java @@ -41,7 +41,7 @@ import org.sonar.core.issue.DefaultActionPlan; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssueComment; import org.sonar.core.issue.FieldDiffs; -import org.sonar.core.issue.workflow.Transition; +import org.sonar.server.issue.workflow.Transition; import org.sonar.core.user.DefaultUser; import org.sonar.db.DbClient; import org.sonar.db.DbSession; 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 2e265e2640d..1c7d13a0df5 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 @@ -91,7 +91,6 @@ import org.sonar.batch.scan.measure.MeasureCache; import org.sonar.batch.source.CodeColorizers; import org.sonar.batch.test.TestPlanBuilder; import org.sonar.batch.test.TestableBuilder; -import org.sonar.core.issue.workflow.FunctionExecutor; import org.sonar.core.metric.BatchMetrics; import org.sonar.core.platform.ComponentContainer; @@ -175,7 +174,6 @@ public class ProjectScanContainer extends ComponentContainer { new QualityProfileProvider(), // issues - FunctionExecutor.class, IssueCache.class, DefaultProjectIssues.class, IssueTransition.class, diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java deleted file mode 100644 index 66bd5779bce..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import javax.annotation.Nullable; -import org.sonar.api.issue.Issue; -import org.sonar.api.user.User; - -interface Function { - interface Context { - Issue issue(); - - Context setAssignee(@Nullable User user); - - Context setResolution(@Nullable String s); - - Context setCloseDate(boolean b); - - Context setLine(@Nullable Integer line); - } - - void execute(Context context); -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java deleted file mode 100644 index 316dfe1b692..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import javax.annotation.Nullable; -import org.sonar.api.batch.BatchSide; -import org.sonar.api.issue.Issue; -import org.sonar.api.server.ServerSide; -import org.sonar.api.user.User; -import org.sonar.core.issue.DefaultIssue; -import org.sonar.core.issue.IssueChangeContext; -import org.sonar.core.issue.IssueUpdater; - -@BatchSide -@ServerSide -public class FunctionExecutor { - - private final IssueUpdater updater; - - public FunctionExecutor(IssueUpdater updater) { - this.updater = updater; - } - - public void execute(Function[] functions, DefaultIssue issue, IssueChangeContext changeContext) { - if (functions.length > 0) { - FunctionContext functionContext = new FunctionContext(updater, issue, changeContext); - for (Function function : functions) { - function.execute(functionContext); - } - } - } - - static class FunctionContext implements Function.Context { - private final IssueUpdater updater; - private final DefaultIssue issue; - private final IssueChangeContext changeContext; - - FunctionContext(IssueUpdater updater, DefaultIssue issue, IssueChangeContext changeContext) { - this.updater = updater; - this.issue = issue; - this.changeContext = changeContext; - } - - @Override - public Issue issue() { - return issue; - } - - @Override - public Function.Context setAssignee(@Nullable User user) { - updater.assign(issue, user, changeContext); - return this; - } - - @Override - public Function.Context setResolution(@Nullable String s) { - updater.setResolution(issue, s, changeContext); - return this; - } - - @Override - public Function.Context setCloseDate(boolean b) { - updater.setCloseDate(issue, b ? changeContext.date() : null, changeContext); - return this; - } - - @Override - public Function.Context setLine(@Nullable Integer line) { - updater.setLine(issue, line); - return this; - } - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsBeingClosed.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsBeingClosed.java deleted file mode 100644 index daf3a2ed4aa..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsBeingClosed.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.condition.Condition; -import org.sonar.core.issue.DefaultIssue; - -enum IsBeingClosed implements Condition { - INSTANCE; - - @Override - public boolean matches(Issue issue) { - return ((DefaultIssue) issue).isBeingClosed(); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsManual.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsManual.java deleted file mode 100644 index d47a98a34d6..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsManual.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.condition.Condition; - -enum IsManual implements Condition { - INSTANCE; - - @Override - public boolean matches(Issue issue) { - return issue.ruleKey().isManual(); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java deleted file mode 100644 index 8ceb48e0d44..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import java.util.List; -import org.picocontainer.Startable; -import org.sonar.api.issue.DefaultTransitions; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.condition.HasResolution; -import org.sonar.api.issue.condition.NotCondition; -import org.sonar.api.server.ServerSide; -import org.sonar.api.web.UserRole; -import org.sonar.core.issue.DefaultIssue; -import org.sonar.core.issue.IssueChangeContext; -import org.sonar.core.issue.IssueUpdater; - -@ServerSide -public class IssueWorkflow implements Startable { - - private final FunctionExecutor functionExecutor; - private final IssueUpdater updater; - private StateMachine machine; - - public IssueWorkflow(FunctionExecutor functionExecutor, IssueUpdater updater) { - this.functionExecutor = functionExecutor; - this.updater = updater; - } - - @Override - public void start() { - StateMachine.Builder builder = StateMachine.builder() - // order is important for UI - .states(Issue.STATUS_OPEN, Issue.STATUS_CONFIRMED, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED, Issue.STATUS_CLOSED); - - buildManualTransitions(builder); - buildAutomaticTransitions(builder); - machine = builder.build(); - } - - private void buildManualTransitions(StateMachine.Builder builder) { - builder.transition(Transition.builder(DefaultTransitions.CONFIRM) - .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED) - .functions(new SetResolution(null)) - .build()) - .transition(Transition.builder(DefaultTransitions.CONFIRM) - .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CONFIRMED) - .functions(new SetResolution(null)) - .build()) - .transition(Transition.builder(DefaultTransitions.UNCONFIRM) - .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_REOPENED) - .functions(new SetResolution(null)) - .build()) - .transition(Transition.builder(DefaultTransitions.RESOLVE) - .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) - .functions(new SetResolution(Issue.RESOLUTION_FIXED)) - .build()) - .transition(Transition.builder(DefaultTransitions.RESOLVE) - .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) - .functions(new SetResolution(Issue.RESOLUTION_FIXED)) - .build()) - .transition(Transition.builder(DefaultTransitions.RESOLVE) - .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED) - .functions(new SetResolution(Issue.RESOLUTION_FIXED)) - .build()) - .transition(Transition.builder(DefaultTransitions.REOPEN) - .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED) - .functions(new SetResolution(null)) - .build()) - .transition(Transition.builder(DefaultTransitions.REOPEN) - .conditions(IsManual.INSTANCE) - .from(Issue.STATUS_CLOSED).to(Issue.STATUS_REOPENED) - .functions(new SetResolution(null), new SetCloseDate(false)) - .build()) - - // resolve as false-positive - .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) - .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) - .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE) - .requiredProjectPermission(UserRole.ISSUE_ADMIN) - .build()) - .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) - .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) - .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE) - .requiredProjectPermission(UserRole.ISSUE_ADMIN) - .build()) - .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) - .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED) - .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE) - .requiredProjectPermission(UserRole.ISSUE_ADMIN) - .build()) - - // resolve as won't fix - .transition(Transition.builder(DefaultTransitions.WONT_FIX) - .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) - .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE) - .requiredProjectPermission(UserRole.ISSUE_ADMIN) - .build()) - .transition(Transition.builder(DefaultTransitions.WONT_FIX) - .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) - .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE) - .requiredProjectPermission(UserRole.ISSUE_ADMIN) - .build()) - .transition(Transition.builder(DefaultTransitions.WONT_FIX) - .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED) - .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE) - .requiredProjectPermission(UserRole.ISSUE_ADMIN) - .build()); - - } - - private void buildAutomaticTransitions(StateMachine.Builder builder) { - // Close the "end of life" issues (disabled/deleted rule, deleted component) - builder - .transition(Transition.builder("automaticclose") - .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED) - .conditions(IsBeingClosed.INSTANCE) - .functions(SetClosed.INSTANCE, new SetCloseDate(true)) - .automatic() - .build()) - .transition(Transition.builder("automaticclose") - .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED) - .conditions(IsBeingClosed.INSTANCE) - .functions(SetClosed.INSTANCE, new SetCloseDate(true)) - .automatic() - .build()) - .transition(Transition.builder("automaticclose") - .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_CLOSED) - .conditions(IsBeingClosed.INSTANCE) - .functions(SetClosed.INSTANCE, new SetCloseDate(true)) - .automatic() - .build()) - .transition(Transition.builder("automaticclose") - .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED) - .conditions(new OrCondition(IsBeingClosed.INSTANCE, IsManual.INSTANCE)) - .functions(SetClosed.INSTANCE, new SetCloseDate(true)) - .automatic() - .build()) - - // Reopen issues that are marked as resolved but that are still alive. - // Manual issues are kept resolved. - .transition(Transition.builder("automaticreopen") - .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED) - .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED), new NotCondition(IsManual.INSTANCE)) - .functions(new SetResolution(null), new SetCloseDate(false)) - .automatic() - .build()); - } - - @Override - public void stop() { - // nothing to do - } - - public boolean doTransition(DefaultIssue issue, String transitionKey, IssueChangeContext issueChangeContext) { - Transition transition = stateOf(issue).transition(transitionKey); - if (transition != null && !transition.automatic()) { - functionExecutor.execute(transition.functions(), issue, issueChangeContext); - updater.setStatus(issue, transition.to(), issueChangeContext); - return true; - } - return false; - } - - public List outTransitions(Issue issue) { - State state = machine.state(issue.status()); - if (state == null) { - throw new IllegalArgumentException("Unknown status: " + issue.status()); - } - return state.outManualTransitions(issue); - } - - public void doAutomaticTransition(DefaultIssue issue, IssueChangeContext issueChangeContext) { - Transition transition = stateOf(issue).outAutomaticTransition(issue); - if (transition != null) { - functionExecutor.execute(transition.functions(), issue, issueChangeContext); - updater.setStatus(issue, transition.to(), issueChangeContext); - } - } - - public List statusKeys() { - return machine.stateKeys(); - } - - private State stateOf(DefaultIssue issue) { - State state = machine.state(issue.status()); - if (state == null) { - throw new IllegalStateException("Unknown status: " + issue.status() + " [issue=" + issue.key() + "]"); - } - return state; - } - - StateMachine machine() { - return machine; - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/OrCondition.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/OrCondition.java deleted file mode 100644 index 094c5d711b2..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/OrCondition.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.condition.Condition; - -public class OrCondition implements Condition { - private final Condition[] conditions; - - public OrCondition(Condition... conditions) { - this.conditions = conditions; - } - - @Override - public boolean matches(Issue issue) { - for (Condition condition : conditions) { - if (condition.matches(issue)) { - return true; - } - } - return false; - } - -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetCloseDate.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetCloseDate.java deleted file mode 100644 index 46f7145fecf..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetCloseDate.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -class SetCloseDate implements Function { - private final boolean set; - - public SetCloseDate(boolean set) { - this.set = set; - } - - @Override - public void execute(Context context) { - context.setCloseDate(set); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosed.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosed.java deleted file mode 100644 index 4b0e5344b4b..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosed.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.sonar.api.issue.Issue; -import org.sonar.core.issue.DefaultIssue; - -public enum SetClosed implements Function { - INSTANCE; - - @Override - public void execute(Context context) { - DefaultIssue issue = (DefaultIssue) context.issue(); - if (issue.isOnDisabledRule()) { - context.setResolution(Issue.RESOLUTION_REMOVED); - } else { - context.setResolution(Issue.RESOLUTION_FIXED); - } - - // closed issues are not "tracked" -> the line number does not evolve anymore - // when code changes. That's misleading for end-users, so line number - // is unset. - context.setLine(null); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java deleted file mode 100644 index 41684d29e63..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import javax.annotation.Nullable; - -public class SetResolution implements Function { - private final String resolution; - - public SetResolution(@Nullable String resolution) { - this.resolution = resolution; - } - - @Override - public void execute(Context context) { - context.setResolution(resolution); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/State.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/State.java deleted file mode 100644 index 8be9b9f5e98..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/State.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import java.util.List; -import java.util.Set; -import javax.annotation.CheckForNull; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.issue.Issue; - -public class State { - private final String key; - private final Transition[] outTransitions; - - public State(String key, Transition[] outTransitions) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "State key must be set"); - Preconditions.checkArgument(StringUtils.isAllUpperCase(key), "State key must be upper-case"); - checkDuplications(outTransitions, key); - - this.key = key; - this.outTransitions = outTransitions; - } - - private static void checkDuplications(Transition[] transitions, String stateKey) { - Set keys = Sets.newHashSet(); - for (Transition transition : transitions) { - if (keys.contains(transition.key())) { - throw new IllegalArgumentException("Transition '" + transition.key() + - "' is declared several times from the originating state '" + stateKey + "'"); - } - keys.add(transition.key()); - } - } - - public List outManualTransitions(Issue issue) { - List result = Lists.newArrayList(); - for (Transition transition : outTransitions) { - if (!transition.automatic() && transition.supports(issue)) { - result.add(transition); - } - } - return result; - } - - @CheckForNull - public Transition outAutomaticTransition(Issue issue) { - Transition result = null; - for (Transition transition : outTransitions) { - if (transition.automatic() && transition.supports(issue)) { - if (result == null) { - result = transition; - } else { - throw new IllegalStateException("Several automatic transitions are available for issue: " + issue); - } - } - } - return result; - } - - Transition transition(String transitionKey) { - for (Transition transition : outTransitions) { - if (transitionKey.equals(transition.key())) { - return transition; - } - } - throw new IllegalStateException("Transition from state " + key + " does not exist: " + transitionKey); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/StateMachine.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/StateMachine.java deleted file mode 100644 index 215e5039d4a..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/StateMachine.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Sets; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.CheckForNull; - -public class StateMachine { - - private final List keys; - private final Map byKey; - - private StateMachine(Builder builder) { - this.keys = ImmutableList.copyOf(builder.states); - ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); - for (String stateKey : builder.states) { - List outTransitions = builder.outTransitions.get(stateKey); - State state = new State(stateKey, outTransitions.toArray(new Transition[outTransitions.size()])); - mapBuilder.put(stateKey, state); - } - byKey = mapBuilder.build(); - } - - @CheckForNull - public State state(String stateKey) { - return byKey.get(stateKey); - } - - public List stateKeys() { - return keys; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private final Set states = Sets.newLinkedHashSet(); - // transitions per originating state - private final ListMultimap outTransitions = ArrayListMultimap.create(); - - private Builder() { - } - - public Builder states(String... keys) { - states.addAll(Arrays.asList(keys)); - return this; - } - - public Builder transition(Transition transition) { - Preconditions.checkArgument(states.contains(transition.from()), "Originating state does not exist: " + transition.from()); - Preconditions.checkArgument(states.contains(transition.to()), "Destination state does not exist: " + transition.to()); - outTransitions.put(transition.from(), transition); - return this; - } - - public StateMachine build() { - Preconditions.checkArgument(!states.isEmpty(), "At least one state is required"); - return new StateMachine(this); - } - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java deleted file mode 100644 index 1bcc5ed54d2..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import java.util.Arrays; -import java.util.List; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.condition.Condition; - -public class Transition { - private final String key; - private final String from; - private final String to; - private final Condition[] conditions; - private final Function[] functions; - private final boolean automatic; - private String requiredProjectPermission; - - private Transition(TransitionBuilder builder) { - key = builder.key; - from = builder.from; - to = builder.to; - conditions = builder.conditions.toArray(new Condition[builder.conditions.size()]); - functions = builder.functions.toArray(new Function[builder.functions.size()]); - automatic = builder.automatic; - requiredProjectPermission = builder.requiredProjectPermission; - } - - public String key() { - return key; - } - - String from() { - return from; - } - - String to() { - return to; - } - - Condition[] conditions() { - return conditions; - } - - Function[] functions() { - return functions; - } - - boolean automatic() { - return automatic; - } - - public boolean supports(Issue issue) { - for (Condition condition : conditions) { - if (!condition.matches(issue)) { - return false; - } - } - return true; - } - - public String requiredProjectPermission() { - return requiredProjectPermission; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Transition that = (Transition) o; - if (!from.equals(that.from)) { - return false; - } - if (!key.equals(that.key)) { - return false; - } - return to.equals(that.to); - } - - @Override - public int hashCode() { - int result = key.hashCode(); - result = 31 * result + from.hashCode(); - result = 31 * result + to.hashCode(); - return result; - } - - @Override - public String toString() { - return String.format("%s->%s->%s", from, key, to); - } - - public static Transition create(String key, String from, String to) { - return builder(key).from(from).to(to).build(); - } - - public static TransitionBuilder builder(String key) { - return new TransitionBuilder(key); - } - - public static class TransitionBuilder { - private final String key; - private String from; - private String to; - private List conditions = Lists.newArrayList(); - private List functions = Lists.newArrayList(); - private boolean automatic = false; - private String requiredProjectPermission; - - private TransitionBuilder(String key) { - this.key = key; - } - - public TransitionBuilder from(String from) { - this.from = from; - return this; - } - - public TransitionBuilder to(String to) { - this.to = to; - return this; - } - - public TransitionBuilder conditions(Condition... c) { - this.conditions.addAll(Arrays.asList(c)); - return this; - } - - public TransitionBuilder functions(Function... f) { - this.functions.addAll(Arrays.asList(f)); - return this; - } - - public TransitionBuilder automatic() { - this.automatic = true; - return this; - } - - public TransitionBuilder requiredProjectPermission(String requiredProjectPermission) { - this.requiredProjectPermission = requiredProjectPermission; - return this; - } - - public Transition build() { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "Transition key must be set"); - Preconditions.checkArgument(StringUtils.isAllLowerCase(key), "Transition key must be lower-case"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(from), "Originating status must be set"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(to), "Destination status must be set"); - return new Transition(this); - } - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetAssignee.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetAssignee.java deleted file mode 100644 index 258fb597160..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetAssignee.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -enum UnsetAssignee implements Function { - INSTANCE; - - @Override - public void execute(Context context) { - context.setAssignee(null); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java deleted file mode 100644 index 40a98550148..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.core.issue.workflow; - -import javax.annotation.ParametersAreNonnullByDefault; - diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsBeingClosedTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsBeingClosedTest.java deleted file mode 100644 index 259193eb77e..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsBeingClosedTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; -import org.sonar.core.issue.DefaultIssue; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.core.issue.workflow.IsBeingClosed.INSTANCE; - -public class IsBeingClosedTest { - - @Test - public void should_be_end_of_life() { - DefaultIssue issue = new DefaultIssue(); - assertThat(INSTANCE.matches(issue.setBeingClosed(true))).isTrue(); - assertThat(INSTANCE.matches(issue.setBeingClosed(false))).isFalse(); - } - -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsManualTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsManualTest.java deleted file mode 100644 index 923d8e77010..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsManualTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; -import org.sonar.api.rule.RuleKey; -import org.sonar.core.issue.DefaultIssue; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.core.issue.workflow.IsManual.INSTANCE; - -public class IsManualTest { - - @Test - public void should_match() { - DefaultIssue issue = new DefaultIssue(); - assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "R1")))).isTrue(); - assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of("java", "R1")))).isFalse(); - } - -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java deleted file mode 100644 index ba3571e4441..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import com.google.common.base.Function; -import com.google.common.collect.Collections2; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import javax.annotation.Nullable; -import org.apache.commons.lang.time.DateUtils; -import org.junit.Test; -import org.sonar.api.issue.DefaultTransitions; -import org.sonar.api.rule.RuleKey; -import org.sonar.core.issue.DefaultIssue; -import org.sonar.core.issue.IssueChangeContext; -import org.sonar.core.issue.IssueUpdater; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; -import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; -import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX; -import static org.sonar.api.issue.Issue.STATUS_CLOSED; -import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; -import static org.sonar.api.issue.Issue.STATUS_OPEN; -import static org.sonar.api.issue.Issue.STATUS_REOPENED; -import static org.sonar.api.issue.Issue.STATUS_RESOLVED; -import static org.sonar.api.rule.RuleKey.MANUAL_REPOSITORY_KEY; - -public class IssueWorkflowTest { - - IssueUpdater updater = new IssueUpdater(); - IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater); - - @Test - public void init_state_machine() { - assertThat(workflow.machine()).isNull(); - workflow.start(); - assertThat(workflow.machine()).isNotNull(); - assertThat(workflow.machine().state(STATUS_OPEN)).isNotNull(); - assertThat(workflow.machine().state(STATUS_CONFIRMED)).isNotNull(); - assertThat(workflow.machine().state(STATUS_CLOSED)).isNotNull(); - assertThat(workflow.machine().state(STATUS_REOPENED)).isNotNull(); - assertThat(workflow.machine().state(STATUS_RESOLVED)).isNotNull(); - workflow.stop(); - } - - @Test - public void list_statuses() { - workflow.start(); - // order is important for UI - assertThat(workflow.statusKeys()).containsSequence(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED); - } - - @Test - public void list_out_transitions_from_status_open() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue().setStatus(STATUS_OPEN); - List transitions = workflow.outTransitions(issue); - assertThat(keys(transitions)).containsOnly("confirm", "falsepositive", "resolve", "wontfix"); - } - - @Test - public void list_out_transitions_from_status_confirmed() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CONFIRMED); - List transitions = workflow.outTransitions(issue); - assertThat(keys(transitions)).containsOnly("unconfirm", "falsepositive", "resolve", "wontfix"); - } - - @Test - public void list_out_transitions_from_status_resolved() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue().setStatus(STATUS_RESOLVED); - List transitions = workflow.outTransitions(issue); - assertThat(keys(transitions)).containsOnly("reopen"); - } - - @Test - public void list_out_transitions_from_status_reopen() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue().setStatus(STATUS_REOPENED); - List transitions = workflow.outTransitions(issue); - assertThat(keys(transitions)).containsOnly("confirm", "resolve", "falsepositive", "wontfix"); - } - - @Test - public void list_no_out_transition_from_status_closed() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CLOSED).setRuleKey(RuleKey.of("java", "R1 ")); - List transitions = workflow.outTransitions(issue); - assertThat(transitions).isEmpty(); - } - - @Test - public void list_out_transitions_from_status_closed_on_manual_issue() { - workflow.start(); - - // Manual issue because of reporter - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setStatus(STATUS_CLOSED) - .setRuleKey(RuleKey.of("manual", "Performance")) - .setReporter("simon"); - - List transitions = workflow.outTransitions(issue); - assertThat(keys(transitions)).containsOnly("reopen"); - } - - @Test - public void fail_if_unknown_status_when_listing_transitions() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue().setStatus("xxx"); - try { - workflow.outTransitions(issue); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Unknown status: xxx"); - } - } - - @Test - public void automatically_close_resolved_issue() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setRuleKey(RuleKey.of("js", "S001")) - .setResolution(RESOLUTION_FIXED) - .setStatus(STATUS_RESOLVED) - .setNew(false) - .setBeingClosed(true); - Date now = new Date(); - workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); - assertThat(issue.status()).isEqualTo(STATUS_CLOSED); - assertThat(issue.closeDate()).isNotNull(); - assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND)); - } - - @Test - public void close_open_dead_issue() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setResolution(null) - .setStatus(STATUS_OPEN) - .setNew(false) - .setBeingClosed(true); - Date now = new Date(); - workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); - assertThat(issue.status()).isEqualTo(STATUS_CLOSED); - assertThat(issue.closeDate()).isNotNull(); - assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND)); - } - - @Test - public void close_reopened_dead_issue() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setResolution(null) - .setStatus(STATUS_REOPENED) - .setNew(false) - .setBeingClosed(true); - Date now = new Date(); - workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); - assertThat(issue.status()).isEqualTo(STATUS_CLOSED); - assertThat(issue.closeDate()).isNotNull(); - assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND)); - } - - @Test - public void close_confirmed_dead_issue() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setResolution(null) - .setStatus(STATUS_CONFIRMED) - .setNew(false) - .setBeingClosed(true); - Date now = new Date(); - workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); - assertThat(issue.status()).isEqualTo(STATUS_CLOSED); - assertThat(issue.closeDate()).isNotNull(); - assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND)); - } - - @Test - public void fail_if_unknown_status_on_automatic_trans() { - workflow.start(); - - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setResolution(RESOLUTION_FIXED) - .setStatus("xxx") - .setNew(false) - .setBeingClosed(true); - try { - workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date())); - fail(); - } catch (IllegalStateException e) { - assertThat(e).hasMessage("Unknown status: xxx [issue=ABCDE]"); - } - } - - @Test - public void flag_as_false_positive() { - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setStatus(STATUS_OPEN) - .setRuleKey(RuleKey.of("squid", "AvoidCycle")) - .setAssignee("morgan"); - - workflow.start(); - workflow.doTransition(issue, DefaultTransitions.FALSE_POSITIVE, IssueChangeContext.createScan(new Date())); - - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FALSE_POSITIVE); - assertThat(issue.status()).isEqualTo(STATUS_RESOLVED); - - // should remove assignee - assertThat(issue.assignee()).isNull(); - } - - @Test - public void wont_fix() { - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setStatus(STATUS_OPEN) - .setRuleKey(RuleKey.of("squid", "AvoidCycle")) - .setAssignee("morgan"); - - workflow.start(); - workflow.doTransition(issue, DefaultTransitions.WONT_FIX, IssueChangeContext.createScan(new Date())); - - assertThat(issue.resolution()).isEqualTo(RESOLUTION_WONT_FIX); - assertThat(issue.status()).isEqualTo(STATUS_RESOLVED); - - // should remove assignee - assertThat(issue.assignee()).isNull(); - } - - /** - * User marks the manual issue as resolved -> issue is automatically - * closed. - */ - @Test - public void automatically_close_resolved_manual_issue() { - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setStatus(STATUS_OPEN) - .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance")); - - workflow.start(); - - assertThat(workflow.outTransitions(issue)).containsOnly( - Transition.create("confirm", "OPEN", "CONFIRMED"), - Transition.create("resolve", "OPEN", "RESOLVED"), - Transition.create("falsepositive", "OPEN", "RESOLVED"), - Transition.create("wontfix", "OPEN", "RESOLVED")); - - workflow.doTransition(issue, "resolve", mock(IssueChangeContext.class)); - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); - assertThat(issue.status()).isEqualTo("RESOLVED"); - - assertThat(workflow.outTransitions(issue)).containsOnly( - Transition.create("reopen", "RESOLVED", "REOPENED")); - - workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); - assertThat(issue.status()).isEqualTo(STATUS_CLOSED); - } - - /** - * Manual issue is fixed because the file does not exist anymore - * or the tracking engine did not find the associated code - * -> the issue is closed - */ - @Test - public void automatically_close_manual_issue_on_deleted_code() { - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setStatus(STATUS_OPEN) - .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance")) - .setBeingClosed(true); - - workflow.start(); - - workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); - assertThat(issue.status()).isEqualTo(STATUS_CLOSED); - } - - /** - * Corner-case : the manual issue was marked as resolved by user but at the same - * time the file or the associated line was deleted. - */ - @Test - public void automatically_close_resolved_manual_issue_on_deleted_code() { - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance")) - - // resolved by user - .setResolution(RESOLUTION_FIXED) - .setStatus(STATUS_RESOLVED) - - // but unmatched by tracking engine - .setBeingClosed(true); - - workflow.start(); - - workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); - assertThat(issue.status()).isEqualTo(STATUS_CLOSED); - } - - @Test - public void manual_issues_be_confirmed_then_kept_open() { - // Manual issue because of reporter - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setStatus(STATUS_OPEN) - .setRuleKey(RuleKey.of("manual", "Performance")) - .setReporter("simon"); - - workflow.start(); - - assertThat(workflow.outTransitions(issue)).containsOnly( - Transition.create("confirm", "OPEN", "CONFIRMED"), - Transition.create("resolve", "OPEN", "RESOLVED"), - Transition.create("falsepositive", "OPEN", "RESOLVED"), - Transition.create("wontfix", "OPEN", "RESOLVED")); - - workflow.doTransition(issue, "confirm", mock(IssueChangeContext.class)); - assertThat(issue.resolution()).isNull(); - assertThat(issue.status()).isEqualTo("CONFIRMED"); - - assertThat(workflow.outTransitions(issue)).containsOnly( - Transition.create("unconfirm", "CONFIRMED", "REOPENED"), - Transition.create("resolve", "CONFIRMED", "RESOLVED"), - Transition.create("falsepositive", "CONFIRMED", "RESOLVED"), - Transition.create("wontfix", "CONFIRMED", "RESOLVED")); - - // keep confirmed and unresolved - workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); - assertThat(issue.resolution()).isNull(); - assertThat(issue.status()).isEqualTo("CONFIRMED"); - - // unconfirm - workflow.doTransition(issue, "unconfirm", mock(IssueChangeContext.class)); - assertThat(issue.resolution()).isNull(); - assertThat(issue.status()).isEqualTo("REOPENED"); - } - - @Test - public void manual_issue_on_removed_rule_be_closed() { - // Manual issue because of reporter - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setStatus(STATUS_OPEN) - .setRuleKey(RuleKey.of("manual", "Performance")) - .setReporter("simon") - .setBeingClosed(true) - .setOnDisabledRule(true); - - workflow.start(); - - workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); - assertThat(issue.resolution()).isEqualTo("REMOVED"); - assertThat(issue.status()).isEqualTo(STATUS_CLOSED); - } - - @Test - public void manual_issue_on_removed_component_be_closed() { - // Manual issue because of reporter - DefaultIssue issue = new DefaultIssue() - .setKey("ABCDE") - .setStatus(STATUS_OPEN) - .setRuleKey(RuleKey.of("manual", "Performance")) - .setReporter("simon") - .setBeingClosed(true) - .setOnDisabledRule(false); - - workflow.start(); - - workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class)); - assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); - assertThat(issue.status()).isEqualTo(STATUS_CLOSED); - } - - private Collection keys(List transitions) { - return Collections2.transform(transitions, new Function() { - @Override - public String apply(@Nullable Transition transition) { - return transition.key(); - } - }); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/OrConditionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/OrConditionTest.java deleted file mode 100644 index b8f3e50e39a..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/OrConditionTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.condition.Condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class OrConditionTest { - - private static final Condition TRUE_CONDITION = new BooleanCondition(true); - private static final Condition FALSE_CONDITION = new BooleanCondition(false); - Issue issue = mock(Issue.class); - - @Test - public void match() { - assertThat(new OrCondition(TRUE_CONDITION).matches(issue)).isTrue(); - assertThat(new OrCondition(FALSE_CONDITION).matches(issue)).isFalse(); - assertThat(new OrCondition(FALSE_CONDITION, TRUE_CONDITION).matches(issue)).isTrue(); - assertThat(new OrCondition(FALSE_CONDITION, FALSE_CONDITION).matches(issue)).isFalse(); - } - - private static class BooleanCondition implements Condition { - private final boolean b; - - public BooleanCondition(boolean b) { - this.b = b; - } - - @Override - public boolean matches(Issue issue) { - return b; - } - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetCloseDateTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetCloseDateTest.java deleted file mode 100644 index 92378fa5f03..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetCloseDateTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -public class SetCloseDateTest { - @Test - public void should_set_close_date() { - SetCloseDate function = new SetCloseDate(true); - Function.Context context = mock(Function.Context.class); - function.execute(context); - verify(context, times(1)).setCloseDate(true); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetClosedTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetClosedTest.java deleted file mode 100644 index 0c4341935ad..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetClosedTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; -import org.sonar.api.issue.Issue; -import org.sonar.core.issue.DefaultIssue; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.sonar.core.issue.workflow.SetClosed.INSTANCE; - -public class SetClosedTest { - - Function.Context context = mock(Function.Context.class); - - @Test - public void should_resolve_as_fixed() { - Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(false); - when(context.issue()).thenReturn(issue); - INSTANCE.execute(context); - verify(context, times(1)).setResolution(Issue.RESOLUTION_FIXED); - } - - @Test - public void should_resolve_as_removed_when_rule_is_disabled() { - Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(true); - when(context.issue()).thenReturn(issue); - INSTANCE.execute(context); - verify(context, times(1)).setResolution(Issue.RESOLUTION_REMOVED); - } - - @Test - public void line_number_must_be_unset() { - Issue issue = new DefaultIssue().setBeingClosed(true).setLine(10); - when(context.issue()).thenReturn(issue); - INSTANCE.execute(context); - verify(context).setLine(null); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java deleted file mode 100644 index c8f690a0863..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -public class SetResolutionTest { - @Test - public void execute() { - SetResolution function = new SetResolution("FIXED"); - Function.Context context = mock(Function.Context.class); - function.execute(context); - verify(context, times(1)).setResolution("FIXED"); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateMachineTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateMachineTest.java deleted file mode 100644 index 4d25dd37ae9..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateMachineTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StateMachineTest { - @Test - public void keep_order_of_state_keys() { - StateMachine machine = StateMachine.builder().states("OPEN", "RESOLVED", "CLOSED").build(); - - assertThat(machine.stateKeys()).containsSequence("OPEN", "RESOLVED", "CLOSED"); - } - - @Test - public void stateKey() { - StateMachine machine = StateMachine.builder() - .states("OPEN", "RESOLVED", "CLOSED") - .transition(Transition.builder("resolve").from("OPEN").to("RESOLVED").build()) - .build(); - - assertThat(machine.state("OPEN")).isNotNull(); - assertThat(machine.state("OPEN").transition("resolve")).isNotNull(); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateTest.java deleted file mode 100644 index 660674a2fd8..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - -public class StateTest { - - Transition t1 = Transition.builder("close").from("OPEN").to("CLOSED").build(); - - @Test - public void key_should_be_set() { - try { - new State("", new Transition[0]); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("State key must be set"); - } - } - - @Test - public void key_should_be_upper_case() { - try { - new State("close", new Transition[0]); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("State key must be upper-case"); - } - } - - @Test - public void no_duplicated_out_transitions() { - try { - new State("CLOSE", new Transition[] {t1, t1}); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Transition 'close' is declared several times from the originating state 'CLOSE'"); - } - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java deleted file mode 100644 index 626dd6b2515..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; -import org.sonar.api.issue.condition.Condition; -import org.sonar.core.issue.DefaultIssue; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TransitionTest { - - Condition condition1 = mock(Condition.class); - Condition condition2 = mock(Condition.class); - Function function1 = mock(Function.class); - Function function2 = mock(Function.class); - - @Test - public void test_builder() throws Exception { - Transition transition = Transition.builder("close") - .from("OPEN").to("CLOSED") - .conditions(condition1, condition2) - .functions(function1, function2) - .build(); - assertThat(transition.key()).isEqualTo("close"); - assertThat(transition.from()).isEqualTo("OPEN"); - assertThat(transition.to()).isEqualTo("CLOSED"); - assertThat(transition.conditions()).containsOnly(condition1, condition2); - assertThat(transition.functions()).containsOnly(function1, function2); - assertThat(transition.automatic()).isFalse(); - } - - @Test - public void test_simplest_transition() throws Exception { - Transition transition = Transition.builder("close") - .from("OPEN").to("CLOSED") - .build(); - assertThat(transition.key()).isEqualTo("close"); - assertThat(transition.from()).isEqualTo("OPEN"); - assertThat(transition.to()).isEqualTo("CLOSED"); - assertThat(transition.conditions()).isEmpty(); - assertThat(transition.functions()).isEmpty(); - } - - @Test - public void key_should_be_set() { - try { - Transition.builder("").from("OPEN").to("CLOSED").build(); - fail(); - } catch (Exception e) { - assertThat(e).hasMessage("Transition key must be set"); - } - } - - @Test - public void key_should_be_lower_case() { - try { - Transition.builder("CLOSE").from("OPEN").to("CLOSED").build(); - fail(); - } catch (Exception e) { - assertThat(e).hasMessage("Transition key must be lower-case"); - } - } - - @Test - public void originating_status_should_be_set() { - try { - Transition.builder("close").from("").to("CLOSED").build(); - fail(); - } catch (Exception e) { - assertThat(e).hasMessage("Originating status must be set"); - } - } - - @Test - public void destination_status_should_be_set() { - try { - Transition.builder("close").from("OPEN").to("").build(); - fail(); - } catch (Exception e) { - assertThat(e).hasMessage("Destination status must be set"); - } - } - - @Test - public void should_verify_conditions() { - DefaultIssue issue = new DefaultIssue(); - Transition transition = Transition.builder("close") - .from("OPEN").to("CLOSED") - .conditions(condition1, condition2) - .build(); - - when(condition1.matches(issue)).thenReturn(true); - when(condition2.matches(issue)).thenReturn(false); - assertThat(transition.supports(issue)).isFalse(); - - when(condition1.matches(issue)).thenReturn(true); - when(condition2.matches(issue)).thenReturn(true); - assertThat(transition.supports(issue)).isTrue(); - } - - @Test - public void test_equals_and_hashCode() throws Exception { - Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED"); - Transition t2 = Transition.create("resolve", "REOPENED", "RESOLVED"); - Transition t3 = Transition.create("confirm", "OPEN", "CONFIRMED"); - - assertThat(t1).isNotEqualTo(t2); - assertThat(t1).isNotEqualTo(t3); - assertThat(t1).isEqualTo(t1); - - assertThat(t1.hashCode()).isEqualTo(t1.hashCode()); - } - - @Test - public void test_toString() throws Exception { - Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED"); - assertThat(t1.toString()).isEqualTo("OPEN->resolve->RESOLVED"); - } - - @Test - public void test_automatic_transition() throws Exception { - Transition transition = Transition.builder("close") - .from("OPEN").to("CLOSED") - .automatic() - .build(); - assertThat(transition.automatic()).isTrue(); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetAssigneeTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetAssigneeTest.java deleted file mode 100644 index 7977dd1adfb..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetAssigneeTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.core.issue.workflow; - -import org.junit.Test; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.sonar.core.issue.workflow.UnsetAssignee.INSTANCE; - -public class UnsetAssigneeTest { - - @Test - public void unassign() { - Function.Context context = mock(Function.Context.class); - INSTANCE.execute(context); - verify(context, times(1)).setAssignee(null); - } -}