diff --git a/.run/go build github.com_murex_tcr.run.xml b/.run/go build github.com_murex_tcr.run.xml deleted file mode 100644 index 7e7dd165..00000000 --- a/.run/go build github.com_murex_tcr.run.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 8989cd52..4d5cedef 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,16 @@ provided as built-in. | scala | sbt | sbt | | typescript | yarn | yarn bazel make | +### TCR Variants + +TCR tool can run several variants of the TCR workflow, inspired by [this blog post](https://medium.com/@tdeniffel/tcr-variants-test-commit-revert-bf6bd84b17d3) +by Thomas Deniffel. + +Some are great as pedagogic tools, some are better for day-to-day use. + +The default variant is "The Relaxed". You can refer to [this page](./variants-doc/tcr_variants.md) +for further details on available variants. + ### Base directory In order to know which files and directories to watch, TCR needs to know on which part of the filesystem it should work. diff --git a/doc/tcr.md b/doc/tcr.md index 5e1c62c8..0426bac1 100644 --- a/doc/tcr.md +++ b/doc/tcr.md @@ -18,7 +18,7 @@ tcr [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -h, --help help for tcr @@ -28,6 +28,7 @@ tcr [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_check.md b/doc/tcr_check.md index 33bd71d4..05e6a56f 100644 --- a/doc/tcr_check.md +++ b/doc/tcr_check.md @@ -46,7 +46,7 @@ tcr check [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -55,6 +55,7 @@ tcr check [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_config.md b/doc/tcr_config.md index 5479e989..d19fc509 100644 --- a/doc/tcr_config.md +++ b/doc/tcr_config.md @@ -24,7 +24,7 @@ tcr config [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -33,6 +33,7 @@ tcr config [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_config_reset.md b/doc/tcr_config_reset.md index 9c219fe9..b0e977a8 100644 --- a/doc/tcr_config_reset.md +++ b/doc/tcr_config_reset.md @@ -24,7 +24,7 @@ tcr config reset [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -33,6 +33,7 @@ tcr config reset [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_config_save.md b/doc/tcr_config_save.md index c19a0090..212ac416 100644 --- a/doc/tcr_config_save.md +++ b/doc/tcr_config_save.md @@ -24,7 +24,7 @@ tcr config save [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -33,6 +33,7 @@ tcr config save [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_config_show.md b/doc/tcr_config_show.md index 8e39c0bf..d86b9f3d 100644 --- a/doc/tcr_config_show.md +++ b/doc/tcr_config_show.md @@ -24,7 +24,7 @@ tcr config show [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -33,6 +33,7 @@ tcr config show [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_info.md b/doc/tcr_info.md index 59aae2b3..a9f7da92 100644 --- a/doc/tcr_info.md +++ b/doc/tcr_info.md @@ -24,7 +24,7 @@ tcr info [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -33,6 +33,7 @@ tcr info [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_log.md b/doc/tcr_log.md index b380a16c..e38c9597 100644 --- a/doc/tcr_log.md +++ b/doc/tcr_log.md @@ -32,7 +32,7 @@ tcr log [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -41,6 +41,7 @@ tcr log [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_mob.md b/doc/tcr_mob.md index 14f49c1f..fb263a56 100644 --- a/doc/tcr_mob.md +++ b/doc/tcr_mob.md @@ -24,7 +24,7 @@ tcr mob [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -33,6 +33,7 @@ tcr mob [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_one-shot.md b/doc/tcr_one-shot.md index f8f00997..24cd6dbc 100644 --- a/doc/tcr_one-shot.md +++ b/doc/tcr_one-shot.md @@ -35,7 +35,7 @@ tcr one-shot [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -44,6 +44,7 @@ tcr one-shot [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_solo.md b/doc/tcr_solo.md index c927ebb7..8d0c52d5 100644 --- a/doc/tcr_solo.md +++ b/doc/tcr_solo.md @@ -24,7 +24,7 @@ tcr solo [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -33,6 +33,7 @@ tcr solo [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_stats.md b/doc/tcr_stats.md index 9542abfb..997ea944 100644 --- a/doc/tcr_stats.md +++ b/doc/tcr_stats.md @@ -52,7 +52,7 @@ tcr stats [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -61,6 +61,7 @@ tcr stats [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/doc/tcr_web.md b/doc/tcr_web.md index fb43a0f8..76da34cd 100644 --- a/doc/tcr_web.md +++ b/doc/tcr_web.md @@ -30,7 +30,7 @@ tcr web [flags] ``` -p, --auto-push enable VCS push after every commit -b, --base-dir string indicate the directory from which TCR is looking for files (default: current directory) - -f, --commit-failures enable committing reverts on tests failure + -F, --commit-failures enable committing reverts on tests failure -c, --config-dir string indicate the directory where TCR configuration is stored (default: current directory) -d, --duration duration set the duration for role rotation countdown timer -l, --language string indicate the programming language to be used by TCR @@ -39,6 +39,7 @@ tcr web [flags] -P, --port-number int indicate port number used by TCR HTTP server in web mode (experimental) (default: 8483) -t, --toolchain string indicate the toolchain to be used by TCR -T, --trace string indicate trace options. Recognized values: none (default), vcs or http + -r, --variant string indicate the variant to be used by TCR: nice (default) or original -V, --vcs string indicate the VCS (version control system) to be used by TCR: git (default) or p4 -w, --work-dir string indicate the directory from which TCR is running (default: current directory) ``` diff --git a/src/checker/check_workflow.go b/src/checker/check_variant.go similarity index 57% rename from src/checker/check_workflow.go rename to src/checker/check_variant.go index 316156d5..3446ca06 100644 --- a/src/checker/check_workflow.go +++ b/src/checker/check_variant.go @@ -25,32 +25,36 @@ package checker import ( "github.com/murex/tcr/checker/model" "github.com/murex/tcr/params" + "github.com/murex/tcr/variant" + "strings" ) -var checkWorkflowRunners []checkPointRunner +var checkVariantRunners []checkPointRunner func init() { - checkWorkflowRunners = []checkPointRunner{ - checkCommitFailures, + checkVariantRunners = []checkPointRunner{ + checkVariantSelection, } } -func checkWorkflowConfiguration(p params.Params) (cg *model.CheckGroup) { - cg = model.NewCheckGroup("TCR workflow configuration") - for _, runner := range checkWorkflowRunners { +func checkVariantConfiguration(p params.Params) (cg *model.CheckGroup) { + cg = model.NewCheckGroup("TCR variant configuration") + for _, runner := range checkVariantRunners { cg.Add(runner(p)...) } return cg } -func checkCommitFailures(p params.Params) (cp []model.CheckPoint) { - switch p.CommitFailures { - case true: - cp = append(cp, model.OkCheckPoint( - "commit-failures is turned on: test-breaking changes will be committed")) - case false: - cp = append(cp, model.OkCheckPoint( - "commit-failures is turned off: test-breaking changes will not be committed")) +func checkVariantSelection(p params.Params) (cp []model.CheckPoint) { + switch variantName := strings.ToLower(p.Variant); variantName { + case variant.Relaxed.Name(), variant.BTCR.Name(), variant.Introspective.Name(): + cp = append(cp, model.OkCheckPoint("selected variant is ", variantName)) + case "original": + cp = append(cp, model.ErrorCheckPoint("original variant is not yet supported")) + case "": + cp = append(cp, model.ErrorCheckPoint("no variant is selected")) + default: + cp = append(cp, model.ErrorCheckPoint("selected variant is not supported: \"", variantName, "\"")) } return cp } diff --git a/src/checker/check_workflow_test.go b/src/checker/check_variant_test.go similarity index 57% rename from src/checker/check_workflow_test.go rename to src/checker/check_variant_test.go index 3b871c1a..adb97c64 100644 --- a/src/checker/check_workflow_test.go +++ b/src/checker/check_variant_test.go @@ -29,37 +29,61 @@ import ( "testing" ) -func Test_check_workflow_configuration(t *testing.T) { +func Test_check_variant_configuration(t *testing.T) { assertCheckGroupRunner(t, - checkWorkflowConfiguration, - &checkWorkflowRunners, + checkVariantConfiguration, + &checkVariantRunners, *params.AParamSet(), - "TCR workflow configuration") + "TCR variant configuration") } -func Test_check_commit_failures(t *testing.T) { +func Test_check_variant_selection(t *testing.T) { tests := []struct { - desc string - value bool - expected []model.CheckPoint + desc string + variantName string + expected []model.CheckPoint }{ { - "turned off", false, + "empty", "", []model.CheckPoint{ - model.OkCheckPoint("commit-failures is turned off: test-breaking changes will not be committed"), + model.ErrorCheckPoint("no variant is selected"), }, }, { - "turned on", true, + "unknown", "unknown-variant", []model.CheckPoint{ - model.OkCheckPoint("commit-failures is turned on: test-breaking changes will be committed"), + model.ErrorCheckPoint("selected variant is not supported: \"unknown-variant\""), + }, + }, + { + "Original", "original", + []model.CheckPoint{ + model.ErrorCheckPoint("original variant is not yet supported"), + }, + }, + { + "Introspective", "introspective", + []model.CheckPoint{ + model.OkCheckPoint("selected variant is introspective"), + }, + }, + { + "BTCR", "btcr", + []model.CheckPoint{ + model.OkCheckPoint("selected variant is btcr"), + }, + }, + { + "Relaxed", "relaxed", + []model.CheckPoint{ + model.OkCheckPoint("selected variant is relaxed"), }, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - p := *params.AParamSet(params.WithCommitFailures(test.value)) - assert.Equal(t, test.expected, checkCommitFailures(p)) + p := *params.AParamSet(params.WithVariant(test.variantName)) + assert.Equal(t, test.expected, checkVariantSelection(p)) }) } } diff --git a/src/checker/runner.go b/src/checker/runner.go index 8a8002f2..05c87446 100644 --- a/src/checker/runner.go +++ b/src/checker/runner.go @@ -61,7 +61,7 @@ var checkGroupRunners = []checkGroupRunner{ checkVCSConfiguration, checkGitEnvironment, checkP4Environment, - checkWorkflowConfiguration, + checkVariantConfiguration, checkMobConfiguration, } diff --git a/src/cli/terminal_ui.go b/src/cli/terminal_ui.go index 7ed8ac23..22a428a5 100644 --- a/src/cli/terminal_ui.go +++ b/src/cli/terminal_ui.go @@ -334,6 +334,7 @@ func (term *TerminalUI) ShowSessionInfo() { term.printInfo("Work Directory: ", info.WorkDir) term.printInfo("Language=", info.LanguageName, ", Toolchain=", info.ToolchainName) term.printVCSInfo(info) + term.printVariant(info) term.printMessageSuffix(info.MessageSuffix) } @@ -352,6 +353,10 @@ func (term *TerminalUI) printVCSInfo(info engine.SessionInfo) { } } +func (term *TerminalUI) printVariant(info engine.SessionInfo) { + term.printInfo("Variant is ", info.Variant) +} + func (term *TerminalUI) printMessageSuffix(suffix string) { if suffix == "" { return diff --git a/src/cli/terminal_ui_test.go b/src/cli/terminal_ui_test.go index 50f49261..d8bee609 100644 --- a/src/cli/terminal_ui_test.go +++ b/src/cli/terminal_ui_test.go @@ -511,7 +511,8 @@ func Test_show_session_info(t *testing.T) { expected := asCyanTraceWithSeparatorLine("Base Directory: fake") + asCyanTrace("Work Directory: fake") + asCyanTrace("Language=fake, Toolchain=fake") + - asYellowTrace("VCS \"fake\" is unknown") + asYellowTrace("VCS \"fake\" is unknown") + + asCyanTrace("Variant is relaxed") assert.Equal(t, expected, capturer.CaptureStdout(func() { term, _, _ := terminalSetup(*params.AParamSet()) diff --git a/src/config/param_commit_failures.go b/src/config/param_variant.go similarity index 72% rename from src/config/param_commit_failures.go rename to src/config/param_variant.go index 1cd7aa8c..e82f9a9f 100644 --- a/src/config/param_commit_failures.go +++ b/src/config/param_variant.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2021 Murex +Copyright (c) 2024 Murex Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,29 @@ SOFTWARE. package config import ( + "github.com/murex/tcr/variant" "github.com/spf13/cobra" ) -// AddCommitFailuresParam adds VCS commit failure parameter to the provided command -func AddCommitFailuresParam(cmd *cobra.Command) *BoolParam { - param := BoolParam{ +// AddVariantParam adds variant parameter to the provided command +func AddVariantParam(cmd *cobra.Command) *StringParam { + param := StringParam{ s: paramSettings{ viperSettings: viperSettings{ enabled: true, - keyPath: "config.git", - name: "commit-failures", + keyPath: "config.tcr", + name: "variant", }, cobraSettings: cobraSettings{ - name: "commit-failures", - shorthand: "f", - usage: "enable committing reverts on tests failure", + name: "variant", + shorthand: "r", + usage: "indicate the variant to be used by TCR: relaxed (default), btcr, or introspective", persistent: true, }, }, - v: paramValueBool{ - value: false, - defaultValue: false, + v: paramValueString{ + value: "", + defaultValue: variant.Relaxed.Name(), }, } param.addToCommand(cmd) diff --git a/src/config/tcr_config.go b/src/config/tcr_config.go index 102502e6..3d966e6a 100644 --- a/src/config/tcr_config.go +++ b/src/config/tcr_config.go @@ -46,7 +46,7 @@ type TcrConfig struct { PollingPeriod *DurationParam MobTimerDuration *DurationParam AutoPush *BoolParam - CommitFailures *BoolParam + Variant *StringParam VCS *StringParam MessageSuffix *StringParam Trace *StringParam @@ -59,7 +59,7 @@ func (c TcrConfig) reset() { c.PollingPeriod.reset() c.MobTimerDuration.reset() c.AutoPush.reset() - c.CommitFailures.reset() + c.Variant.reset() c.VCS.reset() c.MessageSuffix.reset() c.Trace.reset() @@ -200,7 +200,7 @@ func AddParameters(cmd *cobra.Command, defaultDir string) { Config.PollingPeriod = AddPollingPeriodParam(cmd) Config.MobTimerDuration = AddMobTimerDurationParam(cmd) Config.AutoPush = AddAutoPushParam(cmd) - Config.CommitFailures = AddCommitFailuresParam(cmd) + Config.Variant = AddVariantParam(cmd) Config.VCS = AddVCSParam(cmd) Config.MessageSuffix = AddMessageSuffixParam(cmd) Config.Trace = AddTraceParam(cmd) @@ -217,7 +217,7 @@ func UpdateEngineParams(p *params.Params) { p.Toolchain = Config.Toolchain.GetValue() p.PollingPeriod = Config.PollingPeriod.GetValue() p.AutoPush = Config.AutoPush.GetValue() - p.CommitFailures = Config.CommitFailures.GetValue() + p.Variant = Config.Variant.GetValue() p.VCS = Config.VCS.GetValue() p.MessageSuffix = Config.MessageSuffix.GetValue() p.Trace = Config.Trace.GetValue() diff --git a/src/config/tcr_config_test.go b/src/config/tcr_config_test.go index 07dad702..98764950 100644 --- a/src/config/tcr_config_test.go +++ b/src/config/tcr_config_test.go @@ -28,6 +28,7 @@ import ( "github.com/murex/tcr/params" "github.com/murex/tcr/toolchain" "github.com/murex/tcr/utils" + "github.com/murex/tcr/variant" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "os" @@ -238,12 +239,12 @@ func Test_show_tcr_config_with_default_values(t *testing.T) { expected := []string{ "TCR configuration:", fmt.Sprintf("%v.git.auto-push: %v", prefix, false), - fmt.Sprintf("%v.git.commit-failures: %v", prefix, false), fmt.Sprintf("%v.git.polling-period: %v", prefix, 2*time.Second), fmt.Sprintf("%v.mob-timer.duration: %v", prefix, 5*time.Minute), fmt.Sprintf("%v.tcr.language: %v", prefix, ""), fmt.Sprintf("%v.tcr.toolchain: %v", prefix, ""), fmt.Sprintf("%v.tcr.trace: %v", prefix, "none"), + fmt.Sprintf("%v.tcr.variant: %v", prefix, variant.Relaxed), fmt.Sprintf("%v.vcs.name: %v", prefix, "git"), } utils.AssertSimpleTrace(t, expected, diff --git a/src/engine/session_info.go b/src/engine/session_info.go index d150af70..7a302b0e 100644 --- a/src/engine/session_info.go +++ b/src/engine/session_info.go @@ -30,7 +30,7 @@ type SessionInfo struct { ToolchainName string VCSName string VCSSessionSummary string - CommitOnFail bool + Variant string GitAutoPush bool MessageSuffix string } diff --git a/src/engine/tcr.go b/src/engine/tcr.go index ceace219..57942bad 100644 --- a/src/engine/tcr.go +++ b/src/engine/tcr.go @@ -40,6 +40,7 @@ import ( "github.com/murex/tcr/toolchain" "github.com/murex/tcr/toolchain/command" "github.com/murex/tcr/ui" + "github.com/murex/tcr/variant" "github.com/murex/tcr/vcs" "github.com/murex/tcr/vcs/factory" "gopkg.in/tomb.v2" @@ -57,7 +58,7 @@ type ( setVCS(vcsInterface vcs.Interface) ToggleAutoPush() SetAutoPush(flag bool) - SetCommitOnFail(flag bool) + SetVariant(name string) GetCurrentRole() role.Role RunAsDriver() RunAsNavigator() @@ -91,7 +92,7 @@ type ( // roles simultaneously: we wait for it to leave the previous role // before starting a new one roleMutex sync.Mutex - commitOnFail bool + variant *variant.Variant messageSuffix string // shoot channel is used for handling interruptions coming from the UI shoot chan bool @@ -174,7 +175,7 @@ func (tcr *TCREngine) Init(p params.Params) { tcr.setMessageSuffix(p.MessageSuffix) tcr.vcs.EnableAutoPush(p.AutoPush) - tcr.SetCommitOnFail(p.CommitFailures) + tcr.SetVariant(p.Variant) tcr.setMobTimerDuration(p.MobTurnDuration) tcr.ui.ShowRunningMode(tcr.mode) @@ -182,13 +183,15 @@ func (tcr *TCREngine) Init(p params.Params) { tcr.warnIfOnRootBranch(tcr.mode.IsInteractive()) } -// SetCommitOnFail sets VCS commit-on-fail option to the provided value -func (tcr *TCREngine) SetCommitOnFail(flag bool) { - tcr.commitOnFail = flag - if tcr.commitOnFail { - report.PostInfo("Test-breaking changes will be committed") - } else { - report.PostInfo("Test-breaking changes will not be committed") +// SetVariant sets the TCR variant that will be used by TCR engine +func (tcr *TCREngine) SetVariant(name string) { + var err error + tcr.variant, err = variant.Select(name) + if err != nil { + var unsupportedVariantError *variant.UnsupportedVariantError + if errors.As(err, &unsupportedVariantError) { + tcr.handleError(err, true, status.ConfigError) + } } } @@ -584,7 +587,7 @@ func (tcr *TCREngine) commit(event events.TCREvent) { if err != nil { return } - err = tcr.vcs.Commit(false, tcr.wrapCommitMessages(commitMessageOk, &event)...) + err = tcr.vcs.Commit(tcr.wrapCommitMessages(commitMessageOk, &event)...) tcr.handleError(err, false, status.VCSError) if err != nil { return @@ -592,78 +595,72 @@ func (tcr *TCREngine) commit(event events.TCREvent) { tcr.handleError(tcr.vcsPushAuto(), false, status.VCSError) } -func (tcr *TCREngine) revert(event events.TCREvent) { - if tcr.commitOnFail { - err := tcr.commitTestBreakingChanges(event) - tcr.handleError(err, false, status.VCSError) - if err != nil { - return - } - tcr.handleError(tcr.vcsPushAuto(), false, status.VCSError) +func (tcr *TCREngine) revert(e events.TCREvent) { + var err error + + switch *tcr.variant { + case variant.Introspective: + err = tcr.introspectiveRevert(e) + default: + err = tcr.simpleRevert() } - tcr.revertSrcFiles() + + tcr.handleError(err, false, status.VCSError) } -func (tcr *TCREngine) commitTestBreakingChanges(event events.TCREvent) (err error) { - // Create stash with the changes - err = tcr.vcs.Stash(commitMessageFail) +func (tcr *TCREngine) simpleRevert() error { + diffs, err := tcr.vcs.Diff() if err != nil { return err } - // Apply changes back in the working tree - err = tcr.vcs.UnStash(true) - if err != nil { - return err + var reverted int + for _, diff := range diffs { + if tcr.shouldRevertFile(diff.Path) { + err := tcr.revertFile(diff.Path) + if err != nil { + return err + } + reverted++ + } } - // Commit changes with failure message into VCS index - err = tcr.vcs.Add() - if err != nil { - return err + if reverted > 0 { + report.PostWarning(reverted, " file(s) reverted") + } else { + report.PostInfo(tcr.noFilesRevertedMessage()) } - err = tcr.vcs.Commit(false, tcr.wrapCommitMessages(commitMessageFail, &event)...) + return nil +} + +func (tcr *TCREngine) introspectiveRevert(event events.TCREvent) (err error) { + err = tcr.vcs.Add() if err != nil { return err } - // Revert changes (both in VCS index and working tree) - err = tcr.vcs.Revert() + err = tcr.vcs.Commit(tcr.wrapCommitMessages(commitMessageFail, &event)...) if err != nil { return err } - // Amend commit message on revert operation in VCS index - err = tcr.vcs.Commit(false, tcr.wrapCommitMessages(commitMessageRevert, nil)...) + err = tcr.vcs.RollbackLastCommit() if err != nil { return err } - // Re-apply changes in the working tree and get rid of stash - err = tcr.vcs.UnStash(false) + err = tcr.vcs.Commit(tcr.wrapCommitMessages(commitMessageRevert, nil)...) return err } -func (tcr *TCREngine) revertSrcFiles() { - diffs, err := tcr.vcs.Diff() - tcr.handleError(err, false, status.VCSError) - if err != nil { - return - } - var reverted int - for _, diff := range diffs { - if tcr.language.IsSrcFile(diff.Path) { - err := tcr.revertFile(diff.Path) - tcr.handleError(err, false, status.VCSError) - if err == nil { - reverted++ - } - } - } - if reverted > 0 { - report.PostWarning(reverted, " file(s) reverted") - } else { - report.PostInfo("No file reverted (only test files were updated since last commit)") +func (tcr *TCREngine) noFilesRevertedMessage() string { + if *tcr.variant == variant.Relaxed { + return "No file reverted (only test files were updated since last commit)" } + return "No file reverted" +} + +func (tcr *TCREngine) shouldRevertFile(path string) bool { + return *tcr.variant == variant.BTCR || tcr.language.IsSrcFile(path) } func (tcr *TCREngine) revertFile(file string) error { - return tcr.vcs.Restore(file) + return tcr.vcs.RevertLocal(file) } // GetSessionInfo provides the information related to the current TCR session. @@ -677,7 +674,7 @@ func (tcr *TCREngine) GetSessionInfo() SessionInfo { VCSName: tcr.vcs.Name(), VCSSessionSummary: tcr.vcs.SessionSummary(), GitAutoPush: tcr.vcs.IsAutoPushEnabled(), - CommitOnFail: tcr.commitOnFail, + Variant: tcr.variant.Name(), MessageSuffix: tcr.messageSuffix, } } diff --git a/src/engine/tcr_test.go b/src/engine/tcr_test.go index 97e2ad0c..3b543eee 100644 --- a/src/engine/tcr_test.go +++ b/src/engine/tcr_test.go @@ -35,6 +35,7 @@ import ( "github.com/murex/tcr/toolchain" "github.com/murex/tcr/toolchain/command" "github.com/murex/tcr/ui" + "github.com/murex/tcr/variant" "github.com/murex/tcr/vcs" "github.com/murex/tcr/vcs/factory" "github.com/murex/tcr/vcs/fake" @@ -213,9 +214,18 @@ func Test_tcr_operation_end_state(t *testing.T) { status.VCSError, }, { - "revert with VCS restore failure", + "revert with VCS revert local failure", func() { - tcr, _ := initTCREngineWithFakes(nil, nil, fake.Commands{fake.RestoreCommand}, nil) + tcr, _ := initTCREngineWithFakes(nil, nil, fake.Commands{fake.RevertLocalCommand}, nil) + tcr.revert(*events.ATcrEvent()) + }, + status.VCSError, + }, + { + "introspective revert with VCS rollback last commit failure", + func() { + tcr, _ := initTCREngineWithFakes(nil, nil, fake.Commands{fake.RollbackLastCommitCommand}, nil) + tcr.SetVariant(variant.Introspective.Name()) tcr.revert(*events.ATcrEvent()) }, status.VCSError, @@ -231,55 +241,102 @@ func Test_tcr_operation_end_state(t *testing.T) { } } -func Test_tcr_revert_end_state_with_commit_on_fail_enabled(t *testing.T) { +func Test_relaxed_source_revert(t *testing.T) { + tcr, vcsFake := initTCREngineWithFakes(nil, nil, nil, nil) + tcr.revert(*events.ATcrEvent()) + assert.Equal(t, fake.RevertLocalCommand, vcsFake.GetLastCommand()) +} + +func Test_relaxed_doesnt_revert_test_files(t *testing.T) { + tcr, vcsFake := initTCREngineWithFakesWithFileDiffs(nil, nil, nil, nil, + vcs.FileDiffs{ + vcs.NewFileDiff("fake-test", 1, 1), + }) + tcr.revert(*events.ATcrEvent()) + assert.NotEqual(t, fake.RevertLocalCommand, vcsFake.GetLastCommand()) +} + +func Test_variant_specific_reverts(t *testing.T) { testFlags := []struct { - desc string - vcsFailures fake.Commands - expectedStatus status.Status + description string + variant variant.Variant + fileDiffs vcs.FileDiffs + expectedRevertCount int }{ { - "no failure", - nil, - status.Ok, - }, - { - "VCS stash failure", - fake.Commands{fake.StashCommand}, - status.VCSError, - }, - { - "VCS un-stash failure", - fake.Commands{fake.UnStashCommand}, - status.VCSError, + description: "One source file and one test file", + variant: variant.Relaxed, + fileDiffs: vcs.FileDiffs{ + vcs.NewFileDiff("fake-src", 1, 1), + vcs.NewFileDiff("fake-test", 1, 1), + }, + expectedRevertCount: 1, }, { - "VCS add failure", - fake.Commands{fake.AddCommand}, - status.VCSError, + description: "Two source files and no test files", + variant: variant.Relaxed, + fileDiffs: vcs.FileDiffs{ + vcs.NewFileDiff("fake-src", 1, 1), + vcs.NewFileDiff("fake2-src", 1, 1), + }, + expectedRevertCount: 2, }, { - "VCS commit failure", - fake.Commands{fake.CommitCommand}, - status.VCSError, + description: "One source file and one test file", + variant: variant.BTCR, + fileDiffs: vcs.FileDiffs{ + vcs.NewFileDiff("fake-src", 1, 1), + vcs.NewFileDiff("fake-test", 1, 1), + }, + expectedRevertCount: 2, }, { - "VCS revert failure", - fake.Commands{fake.RevertCommand}, - status.VCSError, + description: "Two source files and no test files", + variant: variant.BTCR, + fileDiffs: vcs.FileDiffs{ + vcs.NewFileDiff("fake-src", 1, 1), + vcs.NewFileDiff("fake2-src", 1, 1), + }, + expectedRevertCount: 2, }, } for _, tt := range testFlags { - t.Run(tt.desc, func(t *testing.T) { - status.RecordState(status.Ok) - tcr, _ := initTCREngineWithFakes(nil, nil, tt.vcsFailures, nil) - tcr.SetCommitOnFail(true) + t.Run(tt.description+" in variant "+string(tt.variant), func(t *testing.T) { + sniffer := report.NewSniffer( + func(msg report.Message) bool { + return msg.Type.Category == report.Warning && + msg.Payload.ToString() == fmt.Sprintf("%d file(s) reverted", tt.expectedRevertCount) + }, + ) + tcr, vcsFake := initTCREngineWithFakesWithFileDiffs( + params.AParamSet(params.WithVariant(tt.variant.Name())), + nil, nil, nil, tt.fileDiffs) tcr.revert(*events.ATcrEvent()) - assert.Equal(t, tt.expectedStatus, status.GetCurrentState()) + sniffer.Stop() + assert.Equal(t, fake.RevertLocalCommand, vcsFake.GetLastCommand()) + assert.Equal(t, 1, sniffer.GetMatchCount()) }) } } +func Test_introspective_variant(t *testing.T) { + tcr, vcsFake := initTCREngineWithFakesWithFileDiffs( + params.AParamSet(params.WithVariant(variant.Introspective.Name())), + nil, nil, nil, vcs.FileDiffs{ + vcs.NewFileDiff("fake-src", 1, 1), + vcs.NewFileDiff("fake-test", 1, 1), + }) + + tcr.revert(*events.ATcrEvent()) + assert.Equal(t, []fake.Command{ + fake.AddCommand, + fake.CommitCommand, + fake.RollbackLastCommitCommand, + fake.CommitCommand, + }, vcsFake.GetLastCommands(4)) +} + func Test_tcr_cycle_end_state(t *testing.T) { testFlags := []struct { desc string @@ -323,8 +380,8 @@ func Test_tcr_cycle_end_state(t *testing.T) { status.VCSError, }, { - "with test and VCS restore failure", - toolchain.Operations{toolchain.TestOperation}, fake.Commands{fake.RestoreCommand}, + "with test and VCS revert local failure", + toolchain.Operations{toolchain.TestOperation}, fake.Commands{fake.RevertLocalCommand}, status.VCSError, }, } @@ -341,11 +398,12 @@ func Test_tcr_cycle_end_state(t *testing.T) { } } -func initTCREngineWithFakes( +func initTCREngineWithFakesWithFileDiffs( p *params.Params, toolchainFailures toolchain.Operations, vcsFailures fake.Commands, logItems vcs.LogItems, + fileDiffs vcs.FileDiffs, ) (*TCREngine, *fake.VCSFake) { tchn := registerFakeToolchain(toolchainFailures) lang := registerFakeLanguage(tchn) @@ -365,7 +423,7 @@ func initTCREngineWithFakes( params.WithToolchain(tchn), params.WithMobTimerDuration(p.MobTurnDuration), params.WithAutoPush(p.AutoPush), - params.WithCommitFailures(p.CommitFailures), + params.WithVariant(p.Variant), params.WithPollingPeriod(p.PollingPeriod), params.WithRunMode(p.Mode), params.WithVCS(p.VCS), @@ -379,7 +437,7 @@ func initTCREngineWithFakes( factory.InitVCS = func(_ string, _ string) (vcs.Interface, error) { fakeSettings := fake.Settings{ FailingCommands: vcsFailures, - ChangedFiles: vcs.FileDiffs{vcs.NewFileDiff("fake-src", 1, 1)}, + ChangedFiles: fileDiffs, Logs: logItems, } vcsFake = fake.NewVCSFake(fakeSettings) @@ -393,6 +451,20 @@ func initTCREngineWithFakes( return tcr, vcsFake } +func initTCREngineWithFakes( + p *params.Params, + toolchainFailures toolchain.Operations, + vcsFailures fake.Commands, + logItems vcs.LogItems, +) (*TCREngine, *fake.VCSFake) { + return initTCREngineWithFakesWithFileDiffs( + p, + toolchainFailures, + vcsFailures, + logItems, + vcs.FileDiffs{vcs.NewFileDiff("fake-src", 1, 1)}) +} + func registerFakeToolchain(failures toolchain.Operations) string { f := toolchain.NewFakeToolchain(failures, toolchain.TestStats{}) if err := toolchain.Register(f); err != nil { @@ -447,22 +519,83 @@ func Test_set_auto_push(t *testing.T) { } } -func Test_set_commit_on_fail(t *testing.T) { +func Test_set_variant(t *testing.T) { var tcr TCRInterface testFlags := []struct { - desc string - state bool + variant variant.Variant }{ - {"Turn on", true}, - {"Turn off", false}, + {variant.Relaxed}, + {variant.BTCR}, } for _, tt := range testFlags { - t.Run(tt.desc, func(t *testing.T) { + t.Run(fmt.Sprintf("Variant %v", tt.variant), func(t *testing.T) { tcr, _ = initTCREngineWithFakes(nil, nil, nil, nil) - tcr.SetCommitOnFail(tt.state) - assert.Equal(t, tt.state, tcr.GetSessionInfo().CommitOnFail) + tcr.SetVariant(tt.variant.Name()) + assert.Equal(t, string(tt.variant), tcr.GetSessionInfo().Variant) + }) + } +} + +func Test_variant_commit_subjects(t *testing.T) { + var tcr TCRInterface + testFlags := []struct { + description string + variant variant.Variant + toolchainFailures toolchain.Operations + expected []string + }{ + { + "Introspective with tests passing.", + variant.Introspective, + toolchain.Operations{}, + []string{"✅ TCR - tests passing"}, + }, + { + "Introspective with tests failing.", + variant.Introspective, + toolchain.Operations{toolchain.TestOperation}, + []string{ + "❌ TCR - tests failing", + "⏪ TCR - revert changes", + }, + }, + { + "Relaxed with tests passing.", + variant.Relaxed, + toolchain.Operations{}, + []string{"✅ TCR - tests passing"}, + }, + { + "Relaxed with tests failing.", + variant.Relaxed, + toolchain.Operations{toolchain.TestOperation}, + []string{}, + }, + { + "BTCR with tests passing.", + variant.BTCR, + toolchain.Operations{}, + []string{"✅ TCR - tests passing"}, + }, + { + "BTCR with tests failing.", + variant.BTCR, + toolchain.Operations{toolchain.TestOperation}, + []string{}, + }, + } + + for _, tt := range testFlags { + t.Run(tt.description, func(t *testing.T) { + var vcsFake *fake.VCSFake + tcr, vcsFake = initTCREngineWithFakes(nil, tt.toolchainFailures, nil, nil) + tcr.SetVariant(tt.variant.Name()) + tcr.RunTCRCycle() + messages := vcsFake.GetLastCommitSubjects() + assert.Equal(t, tt.expected, messages) }) } + } func Test_vcs_pull_calls_vcs_command(t *testing.T) { @@ -517,6 +650,7 @@ func Test_get_session_info(t *testing.T) { ToolchainName: "fake-toolchain", VCSName: fake.Name, VCSSessionSummary: "VCS session \"" + fake.Name + "\"", + Variant: variant.Relaxed.Name(), GitAutoPush: false, } assert.Equal(t, expected, tcr.GetSessionInfo()) diff --git a/src/engine/tcr_test_fake.go b/src/engine/tcr_test_fake.go index aa0fac1d..1ca7a05e 100644 --- a/src/engine/tcr_test_fake.go +++ b/src/engine/tcr_test_fake.go @@ -30,6 +30,7 @@ import ( "github.com/murex/tcr/status" "github.com/murex/tcr/timer" "github.com/murex/tcr/ui" + "github.com/murex/tcr/variant" ) // TCRCall is used to track calls to TCR operations @@ -78,7 +79,7 @@ func NewFakeTCREngine() *FakeTCREngine { VCSName: "fake", VCSSessionSummary: "VCS session \"fake\"", GitAutoPush: false, - CommitOnFail: false, + Variant: variant.Relaxed.Name(), }, } } diff --git a/src/http/api/session_info.go b/src/http/api/session_info.go index 452b52e8..c3320709 100644 --- a/src/http/api/session_info.go +++ b/src/http/api/session_info.go @@ -35,6 +35,7 @@ type sessionInfo struct { VCSName string `json:"vcsName"` VCSSessionSummary string `json:"vcsSession"` CommitOnFail bool `json:"commitOnFail"` + Variant string `json:"variant"` GitAutoPush bool `json:"gitAutoPush"` MessageSuffix string `json:"messageSuffix"` } @@ -50,7 +51,7 @@ func SessionInfoGetHandler(c *gin.Context) { ToolchainName: info.ToolchainName, VCSName: info.VCSName, VCSSessionSummary: info.VCSSessionSummary, - CommitOnFail: info.CommitOnFail, + Variant: info.Variant, GitAutoPush: info.GitAutoPush, MessageSuffix: info.MessageSuffix, } diff --git a/src/http/api/session_info_test.go b/src/http/api/session_info_test.go index 00d49641..9fb20a63 100644 --- a/src/http/api/session_info_test.go +++ b/src/http/api/session_info_test.go @@ -56,7 +56,7 @@ func Test_session_info_get_handler(t *testing.T) { ToolchainName: info.ToolchainName, VCSName: info.VCSName, VCSSessionSummary: info.VCSSessionSummary, - CommitOnFail: info.CommitOnFail, + Variant: info.Variant, GitAutoPush: info.GitAutoPush, MessageSuffix: info.MessageSuffix, } diff --git a/src/language/file_tree_filter.go b/src/language/file_tree_filter.go index 3a74bab2..ae753502 100644 --- a/src/language/file_tree_filter.go +++ b/src/language/file_tree_filter.go @@ -83,7 +83,7 @@ func toSlashedPath(input string) string { func (ftf FileTreeFilter) isInFileTree(aPath string, baseDir string) bool { absPath, _ := filepath.Abs(aPath) // If no directory is configured, any path that is under baseDir path is ok - if ftf.Directories == nil || len(ftf.Directories) == 0 { + if len(ftf.Directories) == 0 { if utils.IsSubPathOf(absPath, baseDir) { return true } @@ -104,7 +104,7 @@ func (ftf FileTreeFilter) matches(p string, baseDir string) bool { } if ftf.isInFileTree(p, baseDir) { // If no pattern is set, any file matches as long as it's in the file tree - if ftf.FilePatterns == nil || len(ftf.FilePatterns) == 0 { + if len(ftf.FilePatterns) == 0 { return true } for _, filter := range ftf.FilePatterns { diff --git a/src/language/language_test_fake.go b/src/language/language_test_fake.go index 20dc2252..2d4d6042 100644 --- a/src/language/language_test_fake.go +++ b/src/language/language_test_fake.go @@ -24,7 +24,10 @@ SOFTWARE. package language -import "github.com/murex/tcr/toolchain" +import ( + "github.com/murex/tcr/toolchain" + "strings" +) type allFilesFunc func() ([]string, error) @@ -84,8 +87,8 @@ func (fl *FakeLanguage) AllTestFiles() (result []string, err error) { } // IsSrcFile returns true if the provided filePath is recognized as a source file for this language -func (fl *FakeLanguage) IsSrcFile(_ string) bool { - return true +func (fl *FakeLanguage) IsSrcFile(name string) bool { + return !strings.Contains(name, "test") } // GetName uses real Language behaviour diff --git a/src/params/params.go b/src/params/params.go index 1b68fe0b..ba32c922 100644 --- a/src/params/params.go +++ b/src/params/params.go @@ -36,7 +36,7 @@ type Params struct { Toolchain string MobTurnDuration time.Duration AutoPush bool - CommitFailures bool + Variant string PollingPeriod time.Duration Mode runmode.RunMode VCS string diff --git a/src/params/params_test_data_builder.go b/src/params/params_test_data_builder.go index c60772cc..5fe0f28b 100644 --- a/src/params/params_test_data_builder.go +++ b/src/params/params_test_data_builder.go @@ -39,6 +39,7 @@ func AParamSet(builders ...func(params *Params)) *Params { Toolchain: "", MobTurnDuration: 0, AutoPush: false, + Variant: "relaxed", PollingPeriod: 0, Mode: runmode.OneShot{}, VCS: "git", @@ -107,10 +108,10 @@ func WithAutoPush(value bool) func(params *Params) { } } -// WithCommitFailures sets commit-failures flag to the provided value -func WithCommitFailures(value bool) func(params *Params) { +// WithVariant sets the provided value as the variant to be used +func WithVariant(variant string) func(params *Params) { return func(params *Params) { - params.CommitFailures = value + params.Variant = variant } } diff --git a/src/variant/variant.go b/src/variant/variant.go new file mode 100644 index 00000000..c086989c --- /dev/null +++ b/src/variant/variant.go @@ -0,0 +1,69 @@ +/* +Copyright (c) 2024 Murex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package variant + +import ( + "fmt" + "strings" +) + +// UnsupportedVariantError is returned when the provided Variant name is not supported. +type UnsupportedVariantError struct { + variantName string +} + +// Error returns the error description +func (e *UnsupportedVariantError) Error() string { + return fmt.Sprintf("variant not supported: \"%s\"", e.variantName) +} + +// Variant represents the possible values for the TCR Variant. +// These values are inspired by the following blog-post: +// https://medium.com/@tdeniffel/tcr-variants-test-commit-revert-bf6bd84b17d3 +type Variant string + +// Recognized variant values +const ( + Relaxed Variant = "relaxed" + BTCR Variant = "btcr" + Introspective Variant = "introspective" +) + +var recognized = []Variant{Relaxed, BTCR, Introspective} + +// Select returns a variant instance for the provided name. +// It returns an UnsupportedVariantError if the name is not recognized as a +// valid variant name. +func Select(name string) (*Variant, error) { + for _, variant := range recognized { + if strings.ToLower(name) == strings.ToLower(variant.Name()) { + return &variant, nil + } + } + return nil, &UnsupportedVariantError{name} +} + +// Name returns the variant name +func (v Variant) Name() string { + return string(v) +} diff --git a/src/variant/variant_test.go b/src/variant/variant_test.go new file mode 100644 index 00000000..40d6883f --- /dev/null +++ b/src/variant/variant_test.go @@ -0,0 +1,75 @@ +/* +Copyright (c) 2023 Murex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package variant + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_get_variant_name(t *testing.T) { + tests := []struct { + desc string + variant Variant + expected string + }{ + {"relaxed", Relaxed, "relaxed"}, + {"btcr", BTCR, "btcr"}, + {"introspective", Introspective, "introspective"}, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + assert.Equal(t, test.expected, test.variant.Name()) + }) + } +} + +func Test_select_variant(t *testing.T) { + relaxed, btcr, introspective := Relaxed, BTCR, Introspective + tests := []struct { + name string + expectedVariant *Variant + expectedError error + }{ + {"relaxed", &relaxed, nil}, + {"Relaxed", &relaxed, nil}, + {"btcr", &btcr, nil}, + {"BTCR", &btcr, nil}, + {"introspective", &introspective, nil}, + {"unknown", nil, &UnsupportedVariantError{"unknown"}}, + {"", nil, &UnsupportedVariantError{""}}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + variant, err := Select(test.name) + assert.Equal(t, test.expectedVariant, variant) + assert.Equal(t, test.expectedError, err) + }) + } +} + +func Test_unsupported_variant_message_format(t *testing.T) { + err := UnsupportedVariantError{"some-variant"} + assert.Equal(t, "variant not supported: \"some-variant\"", err.Error()) +} diff --git a/src/vcs/fake/vcs_test_fake.go b/src/vcs/fake/vcs_test_fake.go index d14d6e62..277a88e0 100644 --- a/src/vcs/fake/vcs_test_fake.go +++ b/src/vcs/fake/vcs_test_fake.go @@ -51,16 +51,14 @@ func (gc Commands) contains(command Command) bool { // List of supported VCS commands const ( - AddCommand Command = "add" - CommitCommand Command = "commit" - DiffCommand Command = "diff" - LogCommand Command = "log" - PullCommand Command = "pull" - PushCommand Command = "push" - RestoreCommand Command = "restore" - RevertCommand Command = "revert" - StashCommand Command = "stash" - UnStashCommand Command = "unStash" + AddCommand Command = "add" + CommitCommand Command = "commit" + DiffCommand Command = "diff" + LogCommand Command = "log" + PullCommand Command = "pull" + PushCommand Command = "push" + RevertLocalCommand Command = "revertLocal" + RollbackLastCommitCommand Command = "rollbackLastCommit" ) type ( @@ -75,14 +73,15 @@ type ( // VCSFake provides a fake implementation of the VCS interface VCSFake struct { - settings Settings - pushEnabled bool - lastCommand Command + settings Settings + pushEnabled bool + lastCommands []Command + lastCommitSubjects []string } ) func (vf *VCSFake) fakeCommand(cmd Command) (err error) { - vf.lastCommand = cmd + vf.lastCommands = append(vf.lastCommands, cmd) if vf.settings.FailingCommands.contains(cmd) { err = errors.New(vf.Name() + " " + string(cmd) + " error") } @@ -92,7 +91,7 @@ func (vf *VCSFake) fakeCommand(cmd Command) (err error) { // NewVCSFake initializes a fake VCS implementation which does nothing // apart from emulating errors on VCS operations func NewVCSFake(settings Settings) *VCSFake { - return &VCSFake{settings: settings} + return &VCSFake{settings: settings, lastCommitSubjects: make([]string, 0), lastCommands: make([]Command, 0)} } // Name returns VCS name @@ -107,7 +106,12 @@ func (vf *VCSFake) SessionSummary() string { // GetLastCommand returns the last command called func (vf *VCSFake) GetLastCommand() Command { - return vf.lastCommand + return vf.GetLastCommands(1)[0] +} + +// GetLastCommands returns the last commands called +func (vf *VCSFake) GetLastCommands(count int) []Command { + return vf.lastCommands[len(vf.lastCommands)-count:] } // Add does nothing. Returns an error if in the list of failing commands @@ -116,13 +120,14 @@ func (vf *VCSFake) Add(_ ...string) error { } // Commit does nothing. Returns an error if in the list of failing commands -func (vf *VCSFake) Commit(_ bool, _ ...string) error { +func (vf *VCSFake) Commit(messages ...string) error { + vf.lastCommitSubjects = append(vf.lastCommitSubjects, messages[0]) return vf.fakeCommand(CommitCommand) } -// Restore does nothing. Returns an error if in the list of failing commands -func (vf *VCSFake) Restore(_ string) error { - return vf.fakeCommand(RestoreCommand) +// RevertLocal does nothing. Returns an error if in the list of failing commands +func (vf *VCSFake) RevertLocal(_ string) error { + return vf.fakeCommand(RevertLocalCommand) } // Push does nothing. Returns an error if in the list of failing commands @@ -157,19 +162,9 @@ func (vf *VCSFake) Log(msgFilter func(msg string) bool) (logs vcs.LogItems, err return } -// Stash does nothing. Returns an error if in the list of failing commands -func (vf *VCSFake) Stash(_ string) error { - return vf.fakeCommand(StashCommand) -} - -// UnStash does nothing. Returns an error if in the list of failing commands -func (vf *VCSFake) UnStash(_ bool) error { - return vf.fakeCommand(UnStashCommand) -} - -// Revert does nothing. Returns an error if in the list of failing commands -func (vf *VCSFake) Revert() error { - return vf.fakeCommand(RevertCommand) +// RollbackLastCommit does nothing. Returns an error if in the list of failing commands +func (vf *VCSFake) RollbackLastCommit() error { + return vf.fakeCommand(RollbackLastCommitCommand) } // GetRootDir returns the root directory path @@ -211,3 +206,7 @@ func (vf *VCSFake) IsRemoteEnabled() bool { func (vf *VCSFake) CheckRemoteAccess() bool { return vf.settings.RemoteAccessWorking } + +func (vf *VCSFake) GetLastCommitSubjects() []string { + return vf.lastCommitSubjects +} diff --git a/src/vcs/git/git_impl.go b/src/vcs/git/git_impl.go index fbff03b1..b912a4c2 100644 --- a/src/vcs/git/git_impl.go +++ b/src/vcs/git/git_impl.go @@ -225,11 +225,8 @@ func (g *gitImpl) Add(paths ...string) error { // Commit commits changes to git index. // Current implementation uses a direct call to git -func (g *gitImpl) Commit(amend bool, messages ...string) error { +func (g *gitImpl) Commit(messages ...string) error { gitArgs := []string{"commit", "--no-gpg-sign"} - if amend { - gitArgs = append(gitArgs, "--amend") - } for _, message := range messages { gitArgs = append(gitArgs, "-m", message) } @@ -251,18 +248,18 @@ func (g *gitImpl) nothingToCommit() bool { return status.IsClean() } -// Restore restores to last commit for the provided path. +// RevertLocal restores to last commit for the provided path. // Current implementation uses a direct call to git -func (g *gitImpl) Restore(path string) error { +func (g *gitImpl) RevertLocal(path string) error { report.PostWarning("Reverting ", path) return g.traceGit("checkout", "HEAD", "--", path) } -// Revert runs a git revert operation. +// RollbackLastCommit runs a git revert operation. // Current implementation uses a direct call to git -func (g *gitImpl) Revert() error { +func (g *gitImpl) RollbackLastCommit() error { report.PostInfo("Reverting changes") - return g.traceGit("revert", "--no-gpg-sign", "--no-edit", "HEAD") + return g.traceGit("revert", "--no-gpg-sign", "--no-edit", "--no-commit", "HEAD") } // Push runs a git push operation. @@ -292,25 +289,6 @@ func (g *gitImpl) Pull() error { return g.traceGit("pull", "--no-recurse-submodules", g.GetRemoteName(), g.GetWorkingBranch()) } -// Stash creates a git stash. -// Current implementation uses a direct call to git -func (g *gitImpl) Stash(message string) error { - report.PostInfo("Stashing changes") - return g.traceGit("stash", "push", "--quiet", "--include-untracked", "--message", message) -} - -// UnStash applies a git stash. Depending on the keep argument value, either a "stash apply" or a "stash pop" -// command is executed under the hood. -// Current implementation uses a direct call to git -func (g *gitImpl) UnStash(keep bool) error { - report.PostInfo("Applying stashed changes") - stashAction := "pop" - if keep { - stashAction = "apply" - } - return g.traceGit("stash", stashAction, "--quiet") -} - // Diff returns the list of files modified since last commit with diff info for each file // Current implementation uses a direct call to git func (g *gitImpl) Diff() (diffs vcs.FileDiffs, err error) { diff --git a/src/vcs/git/git_impl_test.go b/src/vcs/git/git_impl_test.go index 8449cd95..cac9c451 100644 --- a/src/vcs/git/git_impl_test.go +++ b/src/vcs/git/git_impl_test.go @@ -377,46 +377,38 @@ func Test_git_commit(t *testing.T) { testFlags := []struct { desc string messages []string - amend bool gitError error expectError bool expectedArgs []string }{ { "git commit command call succeeds", - []string{"some message"}, false, + []string{"some message"}, nil, false, []string{"commit", "--no-gpg-sign", "-m", "some message"}, }, { "git commit command call fails", - []string{"some message"}, false, + []string{"some message"}, errors.New("git commit error"), false, // We currently ignore git commit errors to handle the case when there's nothing to commit []string{"commit", "--no-gpg-sign", "-m", "some message"}, }, { "with multiple messages", - []string{"main message", "additional message"}, false, + []string{"main message", "additional message"}, nil, false, []string{"commit", "--no-gpg-sign", "-m", "main message", "-m", "additional message"}, }, { "with multi-line messages", - []string{"main message", "- line 1\n- line 2"}, false, + []string{"main message", "- line 1\n- line 2"}, nil, false, []string{"commit", "--no-gpg-sign", "-m", "main message", "-m", "- line 1\n- line 2"}, }, - { - "with amend option", - []string{"some message"}, true, - nil, - false, - []string{"commit", "--no-gpg-sign", "--amend", "-m", "some message"}, - }, } for _, tt := range testFlags { t.Run(tt.desc, func(t *testing.T) { @@ -426,7 +418,7 @@ func Test_git_commit(t *testing.T) { actualArgs = args[2:] return tt.gitError } - err := g.Commit(tt.amend, tt.messages...) + err := g.Commit(tt.messages...) if tt.expectError { assert.Error(t, err) } else { @@ -437,7 +429,7 @@ func Test_git_commit(t *testing.T) { } } -func Test_git_restore(t *testing.T) { +func Test_git_revert_local(t *testing.T) { testFlags := []struct { desc string gitError error @@ -461,7 +453,7 @@ func Test_git_restore(t *testing.T) { return tt.gitError } - err := g.Restore("some-path") + err := g.RevertLocal("some-path") if tt.expectError { assert.Error(t, err) } else { @@ -471,7 +463,7 @@ func Test_git_restore(t *testing.T) { } } -func Test_git_revert(t *testing.T) { +func Test_git_rollback_last_commit(t *testing.T) { testFlags := []struct { desc string gitError error @@ -482,113 +474,13 @@ func Test_git_revert(t *testing.T) { "git revert command call succeeds", nil, false, - []string{"revert", "--no-gpg-sign", "--no-edit", "HEAD"}, + []string{"revert", "--no-gpg-sign", "--no-edit", "--no-commit", "HEAD"}, }, { "git revert command call fails", errors.New("git revert error"), true, - []string{"revert", "--no-gpg-sign", "--no-edit", "HEAD"}, - }, - } - for _, tt := range testFlags { - t.Run(tt.desc, func(t *testing.T) { - var actualArgs []string - g, _ := newGitImpl(inMemoryRepoInit, "") - g.traceGitFunction = func(args ...string) (err error) { - actualArgs = args[2:] - return tt.gitError - } - - err := g.Revert() - if tt.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tt.expectedArgs, actualArgs) - }) - } -} - -func Test_git_stash(t *testing.T) { - testFlags := []struct { - desc string - message string - gitError error - expectError bool - expectedArgs []string - }{ - { - "git stash command call succeeds", - "some message", - nil, - false, - []string{"stash", "push", "--quiet", "--include-untracked", "--message", "some message"}, - }, - { - "git stash command call fails", - "some message", - errors.New("git stash push error"), - true, - []string{"stash", "push", "--quiet", "--include-untracked", "--message", "some message"}, - }, - } - for _, tt := range testFlags { - t.Run(tt.desc, func(t *testing.T) { - var actualArgs []string - g, _ := newGitImpl(inMemoryRepoInit, "") - g.traceGitFunction = func(args ...string) (err error) { - actualArgs = args[2:] - return tt.gitError - } - - err := g.Stash(tt.message) - if tt.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tt.expectedArgs, actualArgs) - }) - } -} - -func Test_git_unstash(t *testing.T) { - testFlags := []struct { - desc string - keep bool - gitError error - expectError bool - expectedArgs []string - }{ - { - "keep stash and git stash command call succeeds", - true, - nil, - false, - []string{"stash", "apply", "--quiet"}, - }, - { - "keep stash and git stash command call fails", - true, - errors.New("git stash apply error"), - true, - []string{"stash", "apply", "--quiet"}, - }, - { - "remove stash and git stash command call succeeds", - false, - nil, - false, - []string{"stash", "pop", "--quiet"}, - }, - { - "remove stash and git stash command call fails", - false, - errors.New("git stash pop error"), - true, - []string{"stash", "pop", "--quiet"}, + []string{"revert", "--no-gpg-sign", "--no-edit", "--no-commit", "HEAD"}, }, } for _, tt := range testFlags { @@ -600,7 +492,7 @@ func Test_git_unstash(t *testing.T) { return tt.gitError } - err := g.UnStash(tt.keep) + err := g.RollbackLastCommit() if tt.expectError { assert.Error(t, err) } else { diff --git a/src/vcs/p4/p4_impl.go b/src/vcs/p4/p4_impl.go index 45bd088f..06f70674 100644 --- a/src/vcs/p4/p4_impl.go +++ b/src/vcs/p4/p4_impl.go @@ -153,7 +153,7 @@ type changeList struct { // Commit commits changes to p4 index. // With current implementation, "amend" parameter is ignored. -func (p *p4Impl) Commit(_ bool, messages ...string) error { +func (p *p4Impl) Commit(messages ...string) error { cl, err := p.createChangeList(messages...) if err != nil { report.PostError(err) @@ -162,8 +162,8 @@ func (p *p4Impl) Commit(_ bool, messages ...string) error { return p.submitChangeList(cl) } -// Restore restores to last commit for the provided path. -func (p *p4Impl) Restore(path string) error { +// RevertLocal restores to last commit for the provided path. +func (p *p4Impl) RevertLocal(path string) error { // in order to work, p4 revert requires that the file be reconciled beforehand err := p.reconcile(path) if err != nil { @@ -173,9 +173,8 @@ func (p *p4Impl) Restore(path string) error { return p.traceP4("revert", path) } -// Revert runs a p4 revert operation. -// TODO: VCS Revert - p4 revert -func (*p4Impl) Revert() error { +// RollbackLastCommit runs a p4 revert operation. +func (*p4Impl) RollbackLastCommit() error { return errors.New("VCS revert operation not yet available for p4") } @@ -195,19 +194,6 @@ func (p *p4Impl) Pull() error { return p.traceP4("sync", path) } -// Stash creates a p4 stash. -// TODO: VCS Stash - p4 ??? -func (*p4Impl) Stash(_ string) error { - return errors.New("VCS stash operation not yet available for p4") -} - -// UnStash applies a p4 stash. Depending on the keep argument value, either a "stash apply" or a "stash pop" -// command is executed under the hood. -// TODO: VCS UnStash - p4 ??? -func (*p4Impl) UnStash(_ bool) error { - return errors.New("VCS unstash operation not yet available for p4") -} - // Diff returns the list of files modified since last commit with diff info for each file func (p *p4Impl) Diff() (diffs vcs.FileDiffs, err error) { var p4Output []byte diff --git a/src/vcs/p4/p4_impl_test.go b/src/vcs/p4/p4_impl_test.go index 1a1180fb..2fe532e5 100644 --- a/src/vcs/p4/p4_impl_test.go +++ b/src/vcs/p4/p4_impl_test.go @@ -393,7 +393,6 @@ func Test_p4_commit(t *testing.T) { testFlags := []struct { desc string messages []string - amend bool p4ChangeError error p4ChangeOutput string p4SubmitError error @@ -402,42 +401,35 @@ func Test_p4_commit(t *testing.T) { }{ { "p4 change and p4 submit command calls succeed", - []string{"some message"}, false, + []string{"some message"}, nil, "change 1234567 created ...", nil, []string{"submit", "-c", "1234567"}, false, }, { "p4 change command call fails", - []string{"some message"}, false, + []string{"some message"}, errors.New("p4 change error"), "", nil, nil, true, }, { "p4 submit command call fails", - []string{"some message"}, false, + []string{"some message"}, nil, "change 1234567 created ...", errors.New("p4 submit error"), []string{"submit", "-c", "1234567"}, true, }, { "with multiple messages", - []string{"main message", "additional message"}, false, + []string{"main message", "additional message"}, nil, "change 1234567 created ...", nil, []string{"submit", "-c", "1234567"}, false, }, { "with multi-line messages", - []string{"main message", "- line 1\n- line 2"}, false, - nil, "change 1234567 created ...", - nil, []string{"submit", "-c", "1234567"}, - false, - }, - { - "with amend option", - []string{"some message"}, true, + []string{"main message", "- line 1\n- line 2"}, nil, "change 1234567 created ...", nil, []string{"submit", "-c", "1234567"}, false, @@ -457,7 +449,7 @@ func Test_p4_commit(t *testing.T) { return tt.p4SubmitError } - err := p.Commit(tt.amend, tt.messages...) + err := p.Commit(tt.messages...) if tt.expectError { assert.Error(t, err) } else { @@ -516,7 +508,7 @@ func Test_p4_submit(t *testing.T) { } } -func Test_p4_restore(t *testing.T) { +func Test_p4_revert_local(t *testing.T) { testFlags := []struct { desc string p4Error error @@ -551,7 +543,7 @@ func Test_p4_restore(t *testing.T) { return tt.p4Error } - err := p.Restore("some-path") + err := p.RevertLocal("some-path") if tt.expectError { assert.Error(t, err) } else { @@ -564,6 +556,48 @@ func Test_p4_restore(t *testing.T) { } } +func Test_p4_rollback_last_commit(t *testing.T) { + t.Skip("Work in progress") + + testFlags := []struct { + desc string + p4Error error + expectError bool + expectedArgs []string + }{ + { + "p4 undo command call succeeds", + nil, + false, + []string{"undo", "--no-gpg-sign", "--no-edit", "--no-commit", "HEAD"}, + }, + { + "git revert command call fails", + errors.New("git revert error"), + true, + []string{"revert", "--no-gpg-sign", "--no-edit", "--no-commit", "HEAD"}, + }, + } + for _, tt := range testFlags { + t.Run(tt.desc, func(t *testing.T) { + var actualArgs []string + p, _ := newP4Impl(inMemoryDepotInit, "", true) + p.traceP4Function = func(args ...string) (err error) { + actualArgs = args[2:] + return tt.p4Error + } + + err := p.RollbackLastCommit() + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.expectedArgs, actualArgs) + }) + } +} + func Test_convert_to_p4_client_path(t *testing.T) { testFlags := []struct { desc string diff --git a/src/vcs/vcs.go b/src/vcs/vcs.go index f510af9a..20e39fa5 100644 --- a/src/vcs/vcs.go +++ b/src/vcs/vcs.go @@ -56,13 +56,11 @@ type Interface interface { GetWorkingBranch() string IsOnRootBranch() bool Add(paths ...string) error - Commit(amend bool, messages ...string) error - Restore(path string) error - Revert() error + Commit(messages ...string) error + RevertLocal(path string) error + RollbackLastCommit() error Push() error Pull() error - Stash(message string) error - UnStash(keep bool) error Diff() (diffs FileDiffs, err error) Log(msgFilter func(msg string) bool) (logs LogItems, err error) EnableAutoPush(flag bool) diff --git a/variants-doc/mmd/generate_png.bash b/variants-doc/mmd/generate_png.bash new file mode 100644 index 00000000..3b9564e4 --- /dev/null +++ b/variants-doc/mmd/generate_png.bash @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +echo "Installing mermaid-cli" +npm update -g @mermaid-js/mermaid-cli || echo "Failed to install mermaid-cli. Aborting" + +repo_root_dir="$(git rev-parse --show-toplevel)" +webapp_images_dir="${repo_root_dir}/webapp/src/assets/images" + +echo "Generating files into ${webapp_images_dir}" +for mmd_file in ./*.mmd; do + png_file="${mmd_file%.mmd}.png" + echo "- Generating ${png_file} from ${mmd_file}" + mmdc --input "${mmd_file}" --output "${webapp_images_dir}/${png_file}" --theme dark --backgroundColor transparent +done diff --git a/variants-doc/mmd/variant-btcr.mmd b/variants-doc/mmd/variant-btcr.mmd new file mode 100644 index 00000000..28768c3a --- /dev/null +++ b/variants-doc/mmd/variant-btcr.mmd @@ -0,0 +1,20 @@ +stateDiagram-v2 + direction LR + state "⚙️ build" as Build + state "⚙️ test" as Test + state "✅ VCS commit (src + tests)" as Commit + state "❌ VCS revert (src + tests)" as Revert + [*] --> Build + Build --> Test: pass + Build --> [*]: fail + Test --> Commit: pass + Test --> Revert: fail + Commit --> [*] + Revert --> [*] + classDef actionClass fill: #0077CC + classDef okClass fill: #006600 + classDef failClass fill: #660000 + class Build actionClass + class Test actionClass + class Commit okClass + class Revert failClass diff --git a/variants-doc/mmd/variant-introspective.mmd b/variants-doc/mmd/variant-introspective.mmd new file mode 100644 index 00000000..b09b2145 --- /dev/null +++ b/variants-doc/mmd/variant-introspective.mmd @@ -0,0 +1,24 @@ +stateDiagram-v2 + direction LR + state "⚙️ build" as Build + state "⚙️ test" as Test + state "✅ VCS commit (src + tests)" as CommitPass + state "❌ VCS commit (src + tests)" as CommitFail + state "⏪ VCS revert (last commit)" as CommitRevert + + [*] --> Build + Build --> Test: pass + Build --> [*]: fail + Test --> CommitPass: pass + Test --> CommitFail: fail + CommitPass --> [*] + CommitFail --> CommitRevert + CommitRevert --> [*] + classDef actionClass fill: #0077CC + classDef okClass fill: #006600 + classDef failClass fill: #660000 + class Build actionClass + class Test actionClass + class CommitPass okClass + class CommitFail failClass + class CommitRevert actionClass diff --git a/variants-doc/mmd/variant-original.mmd b/variants-doc/mmd/variant-original.mmd new file mode 100644 index 00000000..79161ba1 --- /dev/null +++ b/variants-doc/mmd/variant-original.mmd @@ -0,0 +1,16 @@ +stateDiagram-v2 + direction LR + state "⚙️ test" as Test + state "✅ VCS commit (src + tests)" as Commit + state "❌ VCS revert (src + tests)" as Revert + [*] --> Test + Test --> Commit: pass + Test --> Revert: fail + Commit --> [*] + Revert --> [*] + classDef actionClass fill: #0077CC + classDef okClass fill: #006600 + classDef failClass fill: #660000 + class Test actionClass + class Commit okClass + class Revert failClass diff --git a/variants-doc/mmd/variant-relaxed.mmd b/variants-doc/mmd/variant-relaxed.mmd new file mode 100644 index 00000000..1d6e3be8 --- /dev/null +++ b/variants-doc/mmd/variant-relaxed.mmd @@ -0,0 +1,20 @@ +stateDiagram-v2 + direction LR + state "⚙️ build" as Build + state "⚙️ test" as Test + state "✅ VCS commit (src + tests)" as Commit + state "❌ VCS revert (src)" as Revert + [*] --> Build + Build --> Test: pass + Build --> [*]: fail + Test --> Commit: pass + Test --> Revert: fail + Commit --> [*] + Revert --> [*] + classDef actionClass fill: #0077CC + classDef okClass fill: #006600 + classDef failClass fill: #660000 + class Build actionClass + class Test actionClass + class Commit okClass + class Revert failClass diff --git a/variants-doc/tcr_variants.md b/variants-doc/tcr_variants.md new file mode 100644 index 00000000..77ccf95f --- /dev/null +++ b/variants-doc/tcr_variants.md @@ -0,0 +1,64 @@ +# TCR Variants + +TCR tool can run several variants of TCR, inspired by [this blog post](https://medium.com/@tdeniffel/tcr-variants-test-commit-revert-bf6bd84b17d3) +by Thomas Deniffel. + +We encourage you to read through Thomas's blog post for a deeper understanding of how these +variants differ and what each of them can bring. + +The selection of which variant TCR will be applying is done at launch time through +TCR's tool `--variant` (or `-r`) command-line option. + +The variants currently supported by TCR tool are the following: + +- The Original (not yet supported) +- BTCR +- The Relaxed (default) +- The introspective + +The state diagrams below summarize the behavior of each variant. + +## The Original + +```shell +tcr --variant=original +``` + +![TCR Original variant](../webapp/src/assets/images/variant-original.png) + +## BTCR - Build && Test && Commit || Revert + +> This version tries to build the software first. If it fails, nothing is +> executed — no commit, no revert. Compilation-Issue solved. + +```shell +tcr --variant=btcr +``` + +![TCR BTCR variant](../webapp/src/assets/images/variant-btcr.png) + +## The Relaxed + +> The second issue of the original suggestion is that your unit-tests get deleted all the time. +> The idea is, not to reset the ‘test’ folder but only the ‘src’ folder. + +```shell +tcr --variant=relaxed +``` + +The above option `--variant=relaxed` may be omitted as this is the default variant. + +![TCR Relaxed variant](../webapp/src/assets/images/variant-relaxed.png) + +## The Introspective + +This is an extension to the BTCR variant. Instead of simply reverting changes locally, it commits the failing +changes, and straightaway commits a revert commit to get back to a green state. The goal is to be able to +retrospect on one's progress through a task. It's useful during deliberate practice sessions, but could also +serve in day to day coding. + +```shell +tcr --variant=introspective +``` + +![TCR Introspective variant](../webapp/src/assets/images/variant-introspective.png) diff --git a/webapp/src/app/components/tcr-session-info/tcr-session-info.component.css b/webapp/src/app/components/tcr-session-info/tcr-session-info.component.css index 9d525209..86bf7a70 100644 --- a/webapp/src/app/components/tcr-session-info/tcr-session-info.component.css +++ b/webapp/src/app/components/tcr-session-info/tcr-session-info.component.css @@ -26,6 +26,12 @@ section { background-color: #f1f4fa; } +.statechart { + vertical-align: center; + width: auto; + height: auto; +} + .wrap { display: flex; background: white; @@ -36,7 +42,7 @@ section { } .ico-wrap { - margin: auto; + margin: 10px; } .mbr-iconfont { @@ -47,7 +53,7 @@ section { } .vcenter { - margin: auto; + margin: 10px; } .mbr-section-title3 { @@ -78,3 +84,15 @@ p { font-family: 'Source Sans Pro', sans-serif; font-size: 1rem; } + +.table-header { + font-family: 'Source Sans Pro', sans-serif; + font-weight: lighter; + font-size: 1rem; +} + +.table-value { + font-family: 'Source Sans Pro', sans-serif; + font-weight: bold; + font-size: 1.1rem; +} diff --git a/webapp/src/app/components/tcr-session-info/tcr-session-info.component.html b/webapp/src/app/components/tcr-session-info/tcr-session-info.component.html index 70f1e153..30db14b2 100644 --- a/webapp/src/app/components/tcr-session-info/tcr-session-info.component.html +++ b/webapp/src/app/components/tcr-session-info/tcr-session-info.component.html @@ -1,83 +1,93 @@
+

{{ title }}

- -
+
- +
-

Directories

-

Base Directory

-
    -
  • {{ sessionInfo.baseDir }}
  • -
-

Work Directory

-
    -
  • {{ sessionInfo.workDir }}
  • -
+

Workflow Variant: {{ sessionInfo.variant | variantDescription }}

+ {{ sessionInfo.variant | variantDescription }} +

Refer to this blog post + for additional information on TCR variants.

+
-
+
+
- +
-

Language and Toolchain

-

Language

-
    -
  • {{ sessionInfo.language }}
  • -
-

Toolchain

-
    -
  • {{ sessionInfo.toolchain }}
  • -
+

Directories

+ + + + + + + + + +
Base Directory:{{ sessionInfo.baseDir }}
Work Directory:{{ sessionInfo.workDir }}
-
+
- +
-

Version Control System

-

Used VCS

-
    -
  • {{ sessionInfo.vcsName }}
  • -
-

Session Summary

-
    -
  • {{ sessionInfo.vcsSession }}
  • -
+

Language and Toolchain

+ + + + + + + + + +
Language:{{ sessionInfo.language }}
Toolchain:{{ sessionInfo.toolchain }}
-
+
- +
-

Options and Flags

-

VCS Commit Message Suffix

-
    -
  • {{ sessionInfo.messageSuffix | showEmpty }}
  • -
-

VCS - Commit-On-Fail {{ sessionInfo.commitOnFail | onOff }}

-

Git - Auto-Push {{ sessionInfo.gitAutoPush | onOff }}

+

Version Control System

+ + + + + + + + + + + + + +
Used VCS:{{ sessionInfo.vcsName }}
Session Summary:{{ sessionInfo.vcsSession }}
Git Auto-Push:{{ sessionInfo.gitAutoPush | onOff }}
diff --git a/webapp/src/app/components/tcr-session-info/tcr-session-info.component.spec.ts b/webapp/src/app/components/tcr-session-info/tcr-session-info.component.spec.ts index 887ad5ad..9fbdfac6 100644 --- a/webapp/src/app/components/tcr-session-info/tcr-session-info.component.spec.ts +++ b/webapp/src/app/components/tcr-session-info/tcr-session-info.component.spec.ts @@ -28,7 +28,7 @@ import {TcrSessionInfoComponent} from "./tcr-session-info.component"; const sample: TcrSessionInfo = { baseDir: "/my/base/dir", - commitOnFail: false, + variant: "relaxed", gitAutoPush: false, language: "java", messageSuffix: "my-suffix", @@ -65,15 +65,19 @@ describe('TcrSessionInfoComponent', () => { fixture.detectChanges(); }); - it('should be created', () => { - expect(component).toBeTruthy(); - }); + describe('component instance', () => { + it('should be created', () => { + expect(component).toBeTruthy(); + }); - it('should have title "TCR Session Information"', () => { - expect(component.title).toEqual('TCR Session Information'); + it('should have title "TCR Session Information"', () => { + expect(component.title).toEqual('TCR Session Information'); + }); }); - it('should fetch TCR session info on init', () => { - expect(component.sessionInfo).toEqual(sample); + describe('component initialization', () => { + it('should fetch TCR session info on init', () => { + expect(component.sessionInfo).toEqual(sample); + }); }); }); diff --git a/webapp/src/app/components/tcr-session-info/tcr-session-info.component.ts b/webapp/src/app/components/tcr-session-info/tcr-session-info.component.ts index 3ddd3f55..52b8e14d 100644 --- a/webapp/src/app/components/tcr-session-info/tcr-session-info.component.ts +++ b/webapp/src/app/components/tcr-session-info/tcr-session-info.component.ts @@ -23,9 +23,11 @@ SOFTWARE. import {Component, Input, OnInit} from '@angular/core'; import {TcrSessionInfo} from "../../interfaces/tcr-session-info"; import {TcrSessionInfoService} from "../../services/tcr-session-info.service"; -import {DatePipe, NgIf} from "@angular/common"; +import {DatePipe, NgIf, NgOptimizedImage} from "@angular/common"; import {OnOffPipe} from "../../pipes/on-off.pipe"; import {ShowEmptyPipe} from "../../pipes/show-empty.pipe"; +import {VariantDescriptionPipe} from "../../pipes/variant-description.pipe"; +import {VariantImagePathPipe} from "../../pipes/variant-image-path.pipe"; @Component({ selector: 'app-tcr-session-info', @@ -34,7 +36,10 @@ import {ShowEmptyPipe} from "../../pipes/show-empty.pipe"; DatePipe, NgIf, OnOffPipe, - ShowEmptyPipe + ShowEmptyPipe, + NgOptimizedImage, + VariantDescriptionPipe, + VariantImagePathPipe ], templateUrl: './tcr-session-info.component.html', styleUrl: './tcr-session-info.component.css' @@ -55,4 +60,5 @@ export class TcrSessionInfoComponent implements OnInit { this.sessionInfoService.getSessionInfo() .subscribe(sessionInfo => this.sessionInfo = sessionInfo); } + } diff --git a/webapp/src/app/interfaces/tcr-session-info.ts b/webapp/src/app/interfaces/tcr-session-info.ts index 0366831f..a626fb18 100644 --- a/webapp/src/app/interfaces/tcr-session-info.ts +++ b/webapp/src/app/interfaces/tcr-session-info.ts @@ -27,7 +27,31 @@ export interface TcrSessionInfo { toolchain: string; vcsName: string; vcsSession: string; - commitOnFail: boolean; + variant: string; gitAutoPush: boolean; messageSuffix: string; } + +export interface TcrVariant { + description: string; + statechartImageFile: string; +} + +export const tcrVariants: { [key: string]: TcrVariant } = { + "original": { + description: "The Original", + statechartImageFile: "variant-original.png", + }, + "btcr": { + description: "BTCR -- Build && Test && Commit || Revert", + statechartImageFile: "variant-btcr.png", + }, + "relaxed": { + description: "The Relaxed", + statechartImageFile: "variant-relaxed.png", + }, + "introspective": { + description: "The Introspective", + statechartImageFile: "variant-introspective.png", + }, +}; diff --git a/webapp/src/app/pipes/format-timer.pipe.ts b/webapp/src/app/pipes/format-timer.pipe.ts index 6dcc83d2..f081e7bf 100644 --- a/webapp/src/app/pipes/format-timer.pipe.ts +++ b/webapp/src/app/pipes/format-timer.pipe.ts @@ -31,7 +31,8 @@ const SECONDS_PER_HOUR = 3600; standalone: true }) export class FormatTimerPipe implements PipeTransform { - transform(value: unknown, ..._args: unknown[]): unknown { + + transform(value: number | string | null | undefined, ..._args: unknown[]): string { const duration: number = parseInt(value as string, 10); if (isNaN(duration)) { return `--${SEPARATOR}--`; diff --git a/webapp/src/app/pipes/on-off.pipe.ts b/webapp/src/app/pipes/on-off.pipe.ts index b5e356fe..57503105 100644 --- a/webapp/src/app/pipes/on-off.pipe.ts +++ b/webapp/src/app/pipes/on-off.pipe.ts @@ -28,7 +28,7 @@ import {Pipe, PipeTransform} from '@angular/core'; }) export class OnOffPipe implements PipeTransform { - transform(value: unknown, ..._args: unknown[]): unknown { + transform(value: boolean | string | null | undefined, ..._args: unknown[]): string { return value ? "✅" : "❌"; } diff --git a/webapp/src/app/pipes/show-empty.pipe.ts b/webapp/src/app/pipes/show-empty.pipe.ts index 03055727..1ab556b6 100644 --- a/webapp/src/app/pipes/show-empty.pipe.ts +++ b/webapp/src/app/pipes/show-empty.pipe.ts @@ -29,8 +29,7 @@ const NOT_SET: string = "[not set]"; standalone: true }) export class ShowEmptyPipe implements PipeTransform { - transform(value: unknown, ..._args: unknown[]): unknown { + transform(value: string | null | undefined, ..._args: unknown[]): string { return value || NOT_SET; } - } diff --git a/webapp/src/app/pipes/variant-description.pipe.spec.ts b/webapp/src/app/pipes/variant-description.pipe.spec.ts new file mode 100644 index 00000000..12ba2740 --- /dev/null +++ b/webapp/src/app/pipes/variant-description.pipe.spec.ts @@ -0,0 +1,47 @@ +/* +Copyright (c) 2024 Murex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import {VariantDescriptionPipe} from './variant-description.pipe'; + +describe('VariantDescriptionPipe', () => { + let pipe: VariantDescriptionPipe; + + beforeEach(() => { + pipe = new VariantDescriptionPipe(); + }); + + const notSet = '⚠️ [unknown]'; + + [ + {input: 'relaxed', expected: 'The Relaxed'}, + {input: 'btcr', expected: 'BTCR -- Build && Test && Commit || Revert'}, + {input: 'original', expected: 'The Original'}, + {input: 'introspective', expected: 'The Introspective'}, + {input: null, expected: notSet}, + {input: undefined, expected: notSet}, + {input: '', expected: notSet} + ].forEach(testCase => { + it(`should return "${testCase.expected}" when the variant value is "${testCase.input}"`, () => { + expect(pipe.transform(testCase.input)).toEqual(testCase.expected); + }); + }); +}); diff --git a/webapp/src/app/pipes/variant-description.pipe.ts b/webapp/src/app/pipes/variant-description.pipe.ts new file mode 100644 index 00000000..cc7eb3b3 --- /dev/null +++ b/webapp/src/app/pipes/variant-description.pipe.ts @@ -0,0 +1,38 @@ +/* +Copyright (c) 2024 Murex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import {Pipe, PipeTransform} from '@angular/core'; +import {tcrVariants} from "../interfaces/tcr-session-info"; + +const UNKNOWN = "⚠️ [unknown]"; + +@Pipe({ + name: 'variantDescription', + standalone: true +}) +export class VariantDescriptionPipe implements PipeTransform { + + transform(value: string | null | undefined, ..._args: unknown[]): string { + return (value && value in tcrVariants) ? tcrVariants[value].description : UNKNOWN; + } + +} diff --git a/webapp/src/app/pipes/variant-image-path.pipe.spec.ts b/webapp/src/app/pipes/variant-image-path.pipe.spec.ts new file mode 100644 index 00000000..c396f827 --- /dev/null +++ b/webapp/src/app/pipes/variant-image-path.pipe.spec.ts @@ -0,0 +1,45 @@ +/* +Copyright (c) 2024 Murex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import {VariantImagePathPipe} from './variant-image-path.pipe'; + +describe('VariantImagePathPipe', () => { + let pipe: VariantImagePathPipe; + + beforeEach(() => { + pipe = new VariantImagePathPipe(); + }); + + [ + {input: 'relaxed', expected: 'assets/images/variant-relaxed.png'}, + {input: 'btcr', expected: 'assets/images/variant-btcr.png'}, + {input: 'original', expected: 'assets/images/variant-original.png'}, + {input: 'introspective', expected: 'assets/images/variant-introspective.png'}, + {input: null, expected: ''}, + {input: undefined, expected: ''}, + {input: '', expected: ''} + ].forEach(testCase => { + it(`should return "${testCase.expected}" when the variant value is "${testCase.input}"`, () => { + expect(pipe.transform(testCase.input)).toEqual(testCase.expected); + }); + }); +}); diff --git a/webapp/src/app/pipes/variant-image-path.pipe.ts b/webapp/src/app/pipes/variant-image-path.pipe.ts new file mode 100644 index 00000000..50a4abed --- /dev/null +++ b/webapp/src/app/pipes/variant-image-path.pipe.ts @@ -0,0 +1,39 @@ +/* +Copyright (c) 2024 Murex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import {Pipe, PipeTransform} from '@angular/core'; +import {tcrVariants} from "../interfaces/tcr-session-info"; + +const IMAGES_PATH = "assets/images" + +@Pipe({ + name: 'variantImagePath', + standalone: true +}) +export class VariantImagePathPipe implements PipeTransform { + + transform(value: string | null | undefined, ..._args: unknown[]): string { + return (value && value in tcrVariants) + ? `${IMAGES_PATH}/${tcrVariants[value].statechartImageFile}` : ""; + } + +} diff --git a/webapp/src/app/services/tcr-session-info.service.spec.ts b/webapp/src/app/services/tcr-session-info.service.spec.ts index bd9a674a..1d4cd490 100644 --- a/webapp/src/app/services/tcr-session-info.service.spec.ts +++ b/webapp/src/app/services/tcr-session-info.service.spec.ts @@ -57,7 +57,7 @@ describe('TcrSessionInfoService', () => { it('should return session info when called', () => { const sample: TcrSessionInfo = { baseDir: "/my/base/dir", - commitOnFail: false, + variant: "nice", gitAutoPush: false, language: "java", messageSuffix: "my-suffix", diff --git a/webapp/src/assets/.gitkeep b/webapp/src/assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/webapp/src/assets/images/variant-btcr.png b/webapp/src/assets/images/variant-btcr.png new file mode 100644 index 00000000..11b8fe96 Binary files /dev/null and b/webapp/src/assets/images/variant-btcr.png differ diff --git a/webapp/src/assets/images/variant-introspective.png b/webapp/src/assets/images/variant-introspective.png new file mode 100644 index 00000000..38ecc75a Binary files /dev/null and b/webapp/src/assets/images/variant-introspective.png differ diff --git a/webapp/src/assets/images/variant-original.png b/webapp/src/assets/images/variant-original.png new file mode 100644 index 00000000..f0be058a Binary files /dev/null and b/webapp/src/assets/images/variant-original.png differ diff --git a/webapp/src/assets/images/variant-relaxed.png b/webapp/src/assets/images/variant-relaxed.png new file mode 100644 index 00000000..c59fb954 Binary files /dev/null and b/webapp/src/assets/images/variant-relaxed.png differ