diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5de0996 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +/build* +docs/build* +/xml +docs/xml +out +mpich +cmake-build-* +.idea/ +.vs/ +.vscode/ +scripts/variants*.csv +scripts/variants*.xlsx +*venv* +sln/ +CMakeSettings.json +.DS_Store +.cache +install +*.pyc diff --git a/BugPro/BugPro.csproj b/BugPro/BugPro.csproj new file mode 100644 index 0000000..378dff9 --- /dev/null +++ b/BugPro/BugPro.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/BugPro/Program.cs b/BugPro/Program.cs new file mode 100644 index 0000000..9f37d8c --- /dev/null +++ b/BugPro/Program.cs @@ -0,0 +1,193 @@ +using System; +using Stateless; + +namespace BugWorkflow +{ + public sealed class Bug + { + public enum BugState + { + New, + Triage, + WaitingInfo, + Fixing, + WaitFixApprove, + Returned, + Reopened, + Closed + } + + public enum BugTrigger + { + StartTriage, + RequestMoreInfo, + BackToTriage, + StartFix, + FinisFix, + MarkNotABug, + MarkWontFix, + MarkDuplicate, + MarkCannotReproduce, + ResolveFix, + RejectFix, + Reopen + } + + private readonly StateMachine _machine; + + public BugState State => _machine.State; + + public Bug() + { + _machine = new StateMachine(BugState.New); + ConfigureStateMachine(); + } + + private void ConfigureStateMachine() + { + _machine.OnUnhandledTrigger((state, trigger) => + { + throw new InvalidOperationException( + $"триггер '{trigger}' недопустим в состоянии '{state}'."); + }); + + _machine.Configure(BugState.New) + .Permit(BugTrigger.StartTriage, BugState.Triage); + + _machine.Configure(BugState.Triage) + .Permit(BugTrigger.RequestMoreInfo, BugState.WaitingInfo) + .Permit(BugTrigger.StartFix, BugState.Fixing) + .Permit(BugTrigger.MarkNotABug, BugState.Closed) + .Permit(BugTrigger.MarkWontFix, BugState.Closed) + .Permit(BugTrigger.MarkDuplicate, BugState.Closed); + + _machine.Configure(BugState.WaitingInfo) + .Permit(BugTrigger.BackToTriage, BugState.Triage) + .Permit(BugTrigger.StartFix, BugState.Fixing); + + _machine.Configure(BugState.Fixing) + .Permit(BugTrigger.FinisFix, BugState.WaitFixApprove) + .Permit(BugTrigger.RequestMoreInfo, BugState.WaitingInfo) + .Permit(BugTrigger.MarkCannotReproduce, BugState.Returned); + + _machine.Configure(BugState.WaitFixApprove) + .Permit(BugTrigger.ResolveFix, BugState.Closed) + .Permit(BugTrigger.RejectFix, BugState.Returned); + + _machine.Configure(BugState.Reopened) + .Permit(BugTrigger.BackToTriage, BugState.Triage); + + _machine.Configure(BugState.Closed) + .Permit(BugTrigger.Reopen, BugState.Reopened); + } + + public void StartTriage() + { + _machine.Fire(BugTrigger.StartTriage); + } + + public void RequestMoreInfo() + { + _machine.Fire(BugTrigger.RequestMoreInfo); + } + + public void BackToTriage() + { + _machine.Fire(BugTrigger.BackToTriage); + } + + public void StartFix() + { + _machine.Fire(BugTrigger.StartFix); + } + + public void FinishFix() + { + _machine.Fire(BugTrigger.FinisFix); + } + + public void MarkNotABug() + { + _machine.Fire(BugTrigger.MarkNotABug); + } + + public void MarkWontFix() + { + _machine.Fire(BugTrigger.MarkWontFix); + } + + public void MarkDuplicate() + { + _machine.Fire(BugTrigger.MarkDuplicate); + } + + public void MarkCannotReproduce() + { + _machine.Fire(BugTrigger.MarkCannotReproduce); + } + + public void ResolveFix() + { + _machine.Fire(BugTrigger.ResolveFix); + } + + public void RejectFix() + { + _machine.Fire(BugTrigger.RejectFix); + } + + public void Reopen() + { + _machine.Fire(BugTrigger.Reopen); + } + + public void CheckFixResult(bool solved) + { + if (solved) + ResolveFix(); + else + RejectFix(); + } + + public override string ToString() + { + return $"State = {State}"; + } + } + + public static class Program + { + public static void Main() + { + var bug = new Bug(); + Console.WriteLine("начальное состояние: " + bug); + + bug.StartTriage(); + Console.WriteLine("после StartTriage: " + bug); + + bug.StartFix(); + Console.WriteLine("после StartFix: " + bug); + + bug.FinishFix(); + Console.WriteLine("после FinishFix: " + bug); + + bug.CheckFixResult(solved: true); + Console.WriteLine("после CheckFixResult(true): " + bug); + + bug.Reopen(); + Console.WriteLine("после Reopen: " + bug); + + bug.BackToTriage(); + Console.WriteLine("после BackToTriage: " + bug); + + try + { + bug.ResolveFix(); + } + catch (InvalidOperationException ex) + { + Console.WriteLine("Ожидаемая ошибка: " + ex.Message); + } + } + } +} diff --git a/BugTests/BugTests.csproj b/BugTests/BugTests.csproj new file mode 100644 index 0000000..2ad20b5 --- /dev/null +++ b/BugTests/BugTests.csproj @@ -0,0 +1,27 @@ + + + + net9.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/BugTests/UnitTest1.cs b/BugTests/UnitTest1.cs new file mode 100644 index 0000000..f1d48f3 --- /dev/null +++ b/BugTests/UnitTest1.cs @@ -0,0 +1,250 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using BugWorkflow; + +namespace BugWorkflow.Tests +{ + [TestClass] + public class BugTests + { + [TestMethod] + public void NewBug_ShouldHaveInitialStateNew() + { + var bug = new Bug(); + + Assert.AreEqual(Bug.BugState.New, bug.State); + } + + [TestMethod] + public void RequestMoreInfo_FromTriage_ShouldMoveToWaitingInfo() + { + var bug = new Bug(); + bug.StartTriage(); + + bug.RequestMoreInfo(); + + Assert.AreEqual(Bug.BugState.WaitingInfo, bug.State); + } + + [TestMethod] + public void StartTriage_FromNew_ShouldMoveToTriage() + { + var bug = new Bug(); + + bug.StartTriage(); + + Assert.AreEqual(Bug.BugState.Triage, bug.State); + } + + [TestMethod] + public void BackToTriage_FromWaitingInfo_ShouldMoveToTriage() + { + var bug = new Bug(); + bug.StartTriage(); + bug.RequestMoreInfo(); + + bug.BackToTriage(); + + Assert.AreEqual(Bug.BugState.Triage, bug.State); + } + + [TestMethod] + public void StartFix_FromTriage_ShouldMoveToFixing() + { + var bug = new Bug(); + bug.StartTriage(); + + bug.StartFix(); + + Assert.AreEqual(Bug.BugState.Fixing, bug.State); + } + + [TestMethod] + public void FinishFix_FromFixing_ShouldMoveToWaitFixApprove() + { + var bug = new Bug(); + bug.StartTriage(); + bug.StartFix(); + + bug.FinishFix(); + + Assert.AreEqual(Bug.BugState.WaitFixApprove, bug.State); + } + + [TestMethod] + public void StartFix_FromWaitingInfo_ShouldMoveToFixing() + { + var bug = new Bug(); + bug.StartTriage(); + bug.RequestMoreInfo(); + + bug.StartFix(); + + Assert.AreEqual(Bug.BugState.Fixing, bug.State); + } + + [TestMethod] + public void ResolveFix_FromWaitFixApprove_ShouldCloseBug() + { + var bug = new Bug(); + bug.StartTriage(); + bug.StartFix(); + bug.FinishFix(); + + bug.ResolveFix(); + + Assert.AreEqual(Bug.BugState.Closed, bug.State); + } + + [TestMethod] + public void RejectFix_FromWaitFixApprove_ShouldMoveToReturned() + { + var bug = new Bug(); + bug.StartTriage(); + bug.StartFix(); + bug.FinishFix(); + + bug.RejectFix(); + + Assert.AreEqual(Bug.BugState.Returned, bug.State); + } + + [TestMethod] + public void CheckFixResult_False_FromWaitFixApprove_ShouldReturnBug() + { + var bug = new Bug(); + bug.StartTriage(); + bug.StartFix(); + bug.FinishFix(); + + bug.CheckFixResult(false); + + Assert.AreEqual(Bug.BugState.Returned, bug.State); + } + + [TestMethod] + public void CheckFixResult_True_FromWaitFixApprove_ShouldCloseBug() + { + var bug = new Bug(); + bug.StartTriage(); + bug.StartFix(); + bug.FinishFix(); + + bug.CheckFixResult(true); + + Assert.AreEqual(Bug.BugState.Closed, bug.State); + } + + [TestMethod] + public void MarkNotABug_FromTriage_ShouldCloseBug() + { + var bug = new Bug(); + bug.StartTriage(); + + bug.MarkNotABug(); + + Assert.AreEqual(Bug.BugState.Closed, bug.State); + } + + [TestMethod] + public void MarkDuplicate_FromTriage_ShouldCloseBug() + { + var bug = new Bug(); + bug.StartTriage(); + + bug.MarkDuplicate(); + + Assert.AreEqual(Bug.BugState.Closed, bug.State); + } + + [TestMethod] + public void MarkWontFix_FromTriage_ShouldCloseBug() + { + var bug = new Bug(); + bug.StartTriage(); + + bug.MarkWontFix(); + + Assert.AreEqual(Bug.BugState.Closed, bug.State); + } + + [TestMethod] + public void MarkCannotReproduce_FromFixing_ShouldMoveToReturned() + { + var bug = new Bug(); + bug.StartTriage(); + bug.StartFix(); + + bug.MarkCannotReproduce(); + + Assert.AreEqual(Bug.BugState.Returned, bug.State); + } + + [TestMethod] + public void Reopen_FromClosed_ShouldMoveToReopened() + { + var bug = new Bug(); + bug.StartTriage(); + bug.MarkNotABug(); + + bug.Reopen(); + + Assert.AreEqual(Bug.BugState.Reopened, bug.State); + } + + [TestMethod] + public void EnteringTriage_AfterReopen_ShouldMoveToTriage() + { + var bug = new Bug(); + bug.StartTriage(); + bug.MarkWontFix(); + bug.Reopen(); + + bug.BackToTriage(); + + Assert.AreEqual(Bug.BugState.Triage, bug.State); + } + + [TestMethod] + public void BackToTriage_FromReopened_ShouldMoveToTriage() + { + var bug = new Bug(); + bug.StartTriage(); + bug.MarkDuplicate(); + bug.Reopen(); + + bug.BackToTriage(); + + Assert.AreEqual(Bug.BugState.Triage, bug.State); + } + + [TestMethod] + public void ResolveFix_FromFixingWithoutFinishFix_ShouldThrowInvalidOperationException() + { + var bug = new Bug(); + bug.StartTriage(); + bug.StartFix(); + + Assert.ThrowsException(() => bug.ResolveFix()); + } + + [TestMethod] + public void StartFix_FromNew_ShouldThrowInvalidOperationException() + { + var bug = new Bug(); + + Assert.ThrowsException(() => bug.StartFix()); + } + + [TestMethod] + public void Reopen_FromReturned_ShouldThrowInvalidOperationException() + { + var bug = new Bug(); + bug.StartTriage(); + bug.StartFix(); + bug.MarkCannotReproduce(); + + Assert.ThrowsException(() => bug.Reopen()); + } + } +} diff --git a/ST-4.sln b/ST-4.sln index 58ea566..3bd9349 100644 --- a/ST-4.sln +++ b/ST-4.sln @@ -3,6 +3,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BugPro", "BugPro\BugPro.csproj", "{B02934F6-7862-449D-B910-D45427D6BAF1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BugTests", "BugTests\BugTests.csproj", "{CCB4C1B0-0EF2-43FE-9125-5C3C130E3744}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -11,4 +15,14 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B02934F6-7862-449D-B910-D45427D6BAF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B02934F6-7862-449D-B910-D45427D6BAF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B02934F6-7862-449D-B910-D45427D6BAF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B02934F6-7862-449D-B910-D45427D6BAF1}.Release|Any CPU.Build.0 = Release|Any CPU + {CCB4C1B0-0EF2-43FE-9125-5C3C130E3744}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCB4C1B0-0EF2-43FE-9125-5C3C130E3744}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCB4C1B0-0EF2-43FE-9125-5C3C130E3744}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCB4C1B0-0EF2-43FE-9125-5C3C130E3744}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection EndGlobal diff --git a/global.json b/global.json new file mode 100644 index 0000000..230afd4 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "9.0.312", + "rollForward": "latestFeature" + } +}