From 0bcbc4593bf8c1d53d1ffe5b33d00af1dfad6000 Mon Sep 17 00:00:00 2001 From: Thomas Boby Date: Thu, 24 Oct 2024 08:42:01 +0100 Subject: [PATCH 1/7] Add pipeline auto-formatter and config changes from #24 --- .editorconfig | 20 ++++++-------------- .gitattributes | 7 +++++++ .github/workflows/buildAndTest.yml | 4 ++++ 3 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig index eab37e5..2fbe3eb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,23 +1,15 @@ root = true -[*] +[*.{fs,fsi,fsx}] end_of_line = lf -insert_final_newline = true -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true - -[*.{fs,fsx,fsi}] -max_line_length = 100 fsharp_alternative_long_member_definitions = true fsharp_multi_line_lambda_closing_newline = true fsharp_multiline_bracket_style = aligned fsharp_keep_max_number_of_blank_lines = 1 fsharp_align_function_signature_to_indentation = true -fsharp_max_if_then_else_short_width = 0 +fsharp_experimental_keep_indent_in_branch = true +fsharp_bar_before_discriminated_union_declaration = true -fsharp_experimental_elmish = true -fsharp_record_multiline_formatter = number_of_items -fsharp_array_or_list_multiline_formatter = number_of_items -fsharp_max_record_number_of_items = 0 -fsharp_max_array_or_list_number_of_items = 0 +# Expecto looks a bit nicer with stroustrup +[tests/**/*.fs] +fsharp_multiline_bracket_style = stroustrup diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e3ca87a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Automatically normalize line endings +* text=auto + +# Always use lf for F# files +*.fs text eol=lf +*.fsx text eol=lf +*.fsi text eol=lf \ No newline at end of file diff --git a/.github/workflows/buildAndTest.yml b/.github/workflows/buildAndTest.yml index 08dfdd1..3a9c110 100644 --- a/.github/workflows/buildAndTest.yml +++ b/.github/workflows/buildAndTest.yml @@ -12,6 +12,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v4 + - name: Tool restore + run: dotnet tool restore + - name: Format Check + run: dotnet fantomas . --check || echo "The code was not formatted, run `dotnet fantomas .` to format all code." - name: Restore run: dotnet restore - name: Run Build From 81c70d57c21e173f2203e48e02daba4a43df467b Mon Sep 17 00:00:00 2001 From: Thomas Boby Date: Thu, 24 Oct 2024 08:43:54 +0100 Subject: [PATCH 2/7] Add stricter warnings/errors --- Directory.Build.props | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Directory.Build.props b/Directory.Build.props index 6d0ad57..d35d286 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,6 +9,13 @@ true true + + true + $(NoWarn);3186,0042 + $(NoWarn);NU1902 + $(WarnOn);1182 + $(WarnOn);3390 + From 708cedf35d5814d8efb06343a5d803af62afe850 Mon Sep 17 00:00:00 2001 From: Thomas Boby Date: Thu, 24 Oct 2024 08:49:56 +0100 Subject: [PATCH 3/7] Fail pipeline on formatting fail --- .github/workflows/buildAndTest.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/buildAndTest.yml b/.github/workflows/buildAndTest.yml index 3a9c110..8f9d0db 100644 --- a/.github/workflows/buildAndTest.yml +++ b/.github/workflows/buildAndTest.yml @@ -15,7 +15,10 @@ jobs: - name: Tool restore run: dotnet tool restore - name: Format Check - run: dotnet fantomas . --check || echo "The code was not formatted, run `dotnet fantomas .` to format all code." + run: dotnet fantomas . --check + - name: log format failure + if: failure() + run: echo "The code was not formatted, run `dotnet fantomas .` to format all code." - name: Restore run: dotnet restore - name: Run Build From dc95c133fc795da8e03ceb9e5d80af9d76aed1da Mon Sep 17 00:00:00 2001 From: Thomas Boby Date: Thu, 24 Oct 2024 08:49:56 +0100 Subject: [PATCH 4/7] Fail pipeline on formatting fail --- .github/workflows/buildAndTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/buildAndTest.yml b/.github/workflows/buildAndTest.yml index 8f9d0db..680f2bd 100644 --- a/.github/workflows/buildAndTest.yml +++ b/.github/workflows/buildAndTest.yml @@ -18,7 +18,7 @@ jobs: run: dotnet fantomas . --check - name: log format failure if: failure() - run: echo "The code was not formatted, run `dotnet fantomas .` to format all code." + run: echo "The code was not formatted, run 'dotnet fantomas .' to format all code." - name: Restore run: dotnet restore - name: Run Build From 635df0503f2f9c16e1d102d2ba320dd0b9fc10c2 Mon Sep 17 00:00:00 2001 From: Thomas Boby Date: Thu, 24 Oct 2024 08:53:39 +0100 Subject: [PATCH 5/7] Single step CICD --- .github/workflows/buildAndTest.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/buildAndTest.yml b/.github/workflows/buildAndTest.yml index 680f2bd..870df03 100644 --- a/.github/workflows/buildAndTest.yml +++ b/.github/workflows/buildAndTest.yml @@ -15,10 +15,7 @@ jobs: - name: Tool restore run: dotnet tool restore - name: Format Check - run: dotnet fantomas . --check - - name: log format failure - if: failure() - run: echo "The code was not formatted, run 'dotnet fantomas .' to format all code." + run: dotnet fantomas . --check || { if [ $? -eq 99 ]; then echo "The code was not formatted, run 'dotnet fantomas .' to format all code."; exit 1; fi; } - name: Restore run: dotnet restore - name: Run Build From 0d6c9ff65b1ace0dc95d25380d1ceba40239b93e Mon Sep 17 00:00:00 2001 From: Thomas Boby Date: Thu, 24 Oct 2024 08:55:41 +0100 Subject: [PATCH 6/7] Apply formatting --- src/Library.fs | 22 ++++++++----- src/Log.fs | 11 ++----- tests/IntegrationTests.fs | 11 ++++--- tests/UnitTests.fs | 67 ++++++++++++++++++++++++--------------- 4 files changed, 63 insertions(+), 48 deletions(-) diff --git a/src/Library.fs b/src/Library.fs index 07c36a7..a48d7c6 100644 --- a/src/Library.fs +++ b/src/Library.fs @@ -32,7 +32,8 @@ type ChangelogExtensions = (string section.Type, section.ItemCollection |> Seq.map _.MarkdownText - |> String.concat Environment.NewLine)) + |> String.concat Environment.NewLine) + ) [] static member ToTaskItem(unreleased: ChangelogSectionUnreleased) = @@ -51,9 +52,12 @@ type ChangelogExtensions = | false, _ -> None | true, version -> Some - {| version = version - date = section.ToDateTime() - collection = section.SubSectionCollection |}) + {| + version = version + date = section.ToDateTime() + collection = section.SubSectionCollection + |} + ) [] static member ToMarkdown(subsections: ChangelogSubSectionCollection) = @@ -62,12 +66,13 @@ type ChangelogExtensions = subsections |> Seq.fold (fun (builder: StringBuilder) subsection -> - let state = builder.AppendLine $"### {subsection.Type}" - |> (fun x -> x.AppendLine "") + let state = + builder.AppendLine $"### {subsection.Type}" |> (fun x -> x.AppendLine "") subsection.ItemCollection |> Seq.fold (fun (builder: StringBuilder) line -> builder.AppendLine $"- {line.MarkdownText}") state - |> (fun x -> x.AppendLine "")) + |> (fun x -> x.AppendLine "") + ) builder |> _.ToString() |> _.Trim() @@ -146,7 +151,8 @@ type ParseChangeLogs() = for (key, value) in x.collection.ToTaskItemMetadata() do taskItem.SetMetadata(key, value) - taskItem :> ITaskItem) + taskItem :> ITaskItem + ) this.CurrentReleaseChangelog <- mapped[0] this.AllReleasedChangelogs <- mapped diff --git a/src/Log.fs b/src/Log.fs index 0a12007..042d64f 100644 --- a/src/Log.fs +++ b/src/Log.fs @@ -19,10 +19,7 @@ let changelogFileNotFound (filePath: string) = ErrorCode = "IKC0001" HelpKeyword = "Missing Changelog file" Message = "The Changelog file {0} was not found." - MessageArgs = - [| - box filePath - |] + MessageArgs = [| box filePath |] } let invalidChangelog (filePath: string) (error: string) = @@ -30,9 +27,5 @@ let invalidChangelog (filePath: string) (error: string) = ErrorCode = "IKC0002" HelpKeyword = "Invalid Changelog file" Message = "The Changelog file {0} is invalid. The error was: {1}" - MessageArgs = - [| - box filePath - box error - |] + MessageArgs = [| box filePath; box error |] } diff --git a/tests/IntegrationTests.fs b/tests/IntegrationTests.fs index f4fbdcf..a1184db 100644 --- a/tests/IntegrationTests.fs +++ b/tests/IntegrationTests.fs @@ -9,13 +9,15 @@ open Faqt open SimpleExec open Workspace - module Utils = let packAndGetPackageProperties projectName = let packageCache = VirtualWorkspace.``test-package-cache``.``.`` + if Directory.Exists packageCache then Directory.Delete(packageCache, true) + Directory.CreateDirectory packageCache |> ignore + Command.Run( "dotnet", CmdLine.empty @@ -24,6 +26,7 @@ module Utils = |> CmdLine.toString, workingDirectory = Workspace.fixtures.``.`` ) + Command.ReadAsync( "dotnet", CmdLine.empty @@ -39,8 +42,8 @@ module Utils = type StringHelper = [] - static member ReplaceEscapedNewLines (s: string) = - s.ReplaceLineEndings().Replace("\\r\\n","\\n") + static member ReplaceEscapedNewLines(s: string) = + s.ReplaceLineEndings().Replace("\\r\\n", "\\n") [] type IntegrationTests() = @@ -52,7 +55,6 @@ type IntegrationTests() = this.testPackageVersion <- $"0.0.1-test-{suffix}" - // Create a package to be used in the tests // I didn't find a way to test the MSBuild tasks execution using MSBuild only // So each fsproj, will use a package reference to the package created here @@ -78,7 +80,6 @@ type IntegrationTests() = workingDirectory = Workspace.fixtures.``.`` ) - [] member this.``works for absolute path with keep a changelog``() : Task = task { diff --git a/tests/UnitTests.fs b/tests/UnitTests.fs index 32b328f..b66825b 100644 --- a/tests/UnitTests.fs +++ b/tests/UnitTests.fs @@ -8,25 +8,25 @@ open Faqt.Operators open Microsoft.VisualStudio.TestTools.UnitTesting open Workspace -type TestContext = - { - BuildEngine: Mock - Errors: ResizeArray - } +type TestContext = { + BuildEngine: Mock + Errors: ResizeArray +} with member this.PrintErrors() = this.Errors |> Seq.iter (fun error -> printfn "Error: %s" error.Message) + [] type UnitTests() = member val context = Unchecked.defaultof with get, set + [] member this.Initialize() = - this.context <- - { - BuildEngine = Mock() - Errors = ResizeArray() - } + this.context <- { + BuildEngine = Mock() + Errors = ResizeArray() + } this.context.BuildEngine .Setup(fun engine -> engine.LogErrorEvent(It.IsAny())) @@ -34,7 +34,7 @@ type UnitTests() = |> ignore [] - member this.``task fails when changelog file does not exist`` () = + member this.``task fails when changelog file does not exist``() = let myTask = ParseChangeLogs(ChangelogFile = "ThisFileDoesNotExist.md") myTask.BuildEngine <- this.context.BuildEngine.Object @@ -46,7 +46,7 @@ type UnitTests() = %this.context.Errors.[0].Code.Should().Be("IKC0001") [] - member this.``task succeeds when changelog file exists (relative path)`` () = + member this.``task succeeds when changelog file exists (relative path)``() = // When running tests, the working directory is where the dll is located let myTask = ParseChangeLogs(ChangelogFile = "../../../changelogs/CHANGELOG.md") @@ -60,7 +60,7 @@ type UnitTests() = %this.context.Errors.Count.Should().Be(0) [] - member this.``task succeeds when changelog file exists (absolute path)`` () = + member this.``task succeeds when changelog file exists (absolute path)``() = let myTask = ParseChangeLogs(ChangelogFile = Workspace.changelogs.``CHANGELOG.md``) myTask.BuildEngine <- this.context.BuildEngine.Object @@ -70,7 +70,7 @@ type UnitTests() = %this.context.Errors.Count.Should().Be(0) [] - member this.``task fails when changelog file is invalid`` () = + member this.``task fails when changelog file is invalid``() = let myTask = ParseChangeLogs(ChangelogFile = Workspace.changelogs.``CHANGELOG_invalid.md``) @@ -82,9 +82,8 @@ type UnitTests() = %this.context.Errors.Count.Should().Be(1) %this.context.Errors.[0].Code.Should().Be("IKC0002") - [] - member this.``task correctly parses details from changelog file`` () = + member this.``task correctly parses details from changelog file``() = let myTask = ParseChangeLogs(ChangelogFile = Workspace.changelogs.``CHANGELOG_detailed.md``) @@ -93,26 +92,42 @@ type UnitTests() = let success = myTask.Execute() %success.Should().BeTrue "Should have successfully parsed the changelog data" %myTask.AllReleasedChangelogs.Length.Should().Be(9, "Should have 9 versions") - %myTask.CurrentReleaseChangelog.ItemSpec.Should().Be("0.1.8", "Should have the most recent version") - %myTask.CurrentReleaseChangelog.GetMetadata("Date").Should().Be("2022-03-31", "Should have the most recent version's date") - %(myTask.CurrentReleaseChangelog.MetadataNames |> Seq.cast |> _.Should().Contain("Changed", "Should have changed metadata")) - %(myTask.CurrentReleaseChangelog.MetadataNames |> Seq.cast |> _.Should().Contain("Date", "Should have date metadata")) + + %myTask.CurrentReleaseChangelog.ItemSpec + .Should() + .Be("0.1.8", "Should have the most recent version") + + %myTask.CurrentReleaseChangelog + .GetMetadata("Date") + .Should() + .Be("2022-03-31", "Should have the most recent version's date") + + %(myTask.CurrentReleaseChangelog.MetadataNames + |> Seq.cast + |> _.Should().Contain("Changed", "Should have changed metadata")) + + %(myTask.CurrentReleaseChangelog.MetadataNames + |> Seq.cast + |> _.Should().Contain("Date", "Should have date metadata")) [] - member this.``task produces expected markdown`` () = - let myTask = - ParseChangeLogs(ChangelogFile = Workspace.changelogs.``CHANGELOG.md``) + member this.``task produces expected markdown``() = + let myTask = ParseChangeLogs(ChangelogFile = Workspace.changelogs.``CHANGELOG.md``) myTask.BuildEngine <- this.context.BuildEngine.Object let success = myTask.Execute() %success.Should().BeTrue "Should have successfully parsed the changelog data" - %myTask.LatestReleaseNotes.Should().Be("""### Added + + %myTask.LatestReleaseNotes + .Should() + .Be( + """### Added - Created the package ### Changed - Changed something in the package -- Updated the target framework""") - +- Updated the target framework""" + ) From 2a952a0ab2488b4851af2d990f02e82ef52224d3 Mon Sep 17 00:00:00 2001 From: Thomas Boby Date: Fri, 25 Oct 2024 08:42:15 +0100 Subject: [PATCH 7/7] Create line ending assertion helper for tests --- tests/Helpers.fs | 32 +++++++++++++++++++ tests/IntegrationTests.fs | 20 ++++-------- tests/Ionide.KeepAChangelog.Tasks.Test.fsproj | 1 + tests/UnitTests.fs | 4 ++- 4 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 tests/Helpers.fs diff --git a/tests/Helpers.fs b/tests/Helpers.fs new file mode 100644 index 0000000..33e5fa6 --- /dev/null +++ b/tests/Helpers.fs @@ -0,0 +1,32 @@ +module Ionide.KeepAChangelog.Tasks.Test.Helpers + +open System.Runtime.CompilerServices +open Faqt +open Faqt.AssertionHelpers + +[] +type Assertions = + + /// Asserts that the subject is equal to the specified string when CLRF is replaced with LF in both raw and + /// escaped forms. + [] + static member BeLineEndingEquivalent(t: Testable, expected: string, ?because) : And = + use _ = t.Assert() + + if isNull expected then + nullArg (nameof expected) + + if isNull t.Subject then + t.With("Expected", expected).With("But was", t.Subject).Fail(because) + + let expectedNormalised = expected.Replace("\r\n", "\n").Replace("\\r\\n", "\\n") + + let subjectNormalised = t.Subject.Replace("\r\n", "\n").Replace("\\r\\n", "\\n") + + if subjectNormalised <> expectedNormalised then + t + .With("Expected", expectedNormalised) + .With("But was", subjectNormalised) + .Fail(because) + + And(t) diff --git a/tests/IntegrationTests.fs b/tests/IntegrationTests.fs index a1184db..19ab843 100644 --- a/tests/IntegrationTests.fs +++ b/tests/IntegrationTests.fs @@ -1,13 +1,14 @@ module Tests.IntegrationTests open System.IO -open System.Runtime.CompilerServices open System.Threading.Tasks +open Ionide.KeepAChangelog.Tasks.Test open Microsoft.VisualStudio.TestTools.UnitTesting open BlackFox.CommandLine open Faqt open SimpleExec open Workspace +open Helpers module Utils = let packAndGetPackageProperties projectName = @@ -40,11 +41,6 @@ module Utils = workingDirectory = Workspace.fixtures.``.`` ) -type StringHelper = - [] - static member ReplaceEscapedNewLines(s: string) = - s.ReplaceLineEndings().Replace("\\r\\n", "\\n") - [] type IntegrationTests() = @@ -90,9 +86,8 @@ type IntegrationTests() = let! struct (stdout, _) = Utils.packAndGetPackageProperties projectName stdout - .ReplaceEscapedNewLines() .Should() - .Be( + .BeLineEndingEquivalent( """{ "Properties": { "Version": "0.1.0", @@ -115,9 +110,8 @@ type IntegrationTests() = let! struct (stdout, _) = Utils.packAndGetPackageProperties projectName stdout - .ReplaceEscapedNewLines() .Should() - .Be( + .BeLineEndingEquivalent( """{ "Properties": { "Version": "0.1.0", @@ -140,9 +134,8 @@ type IntegrationTests() = let! struct (stdout, _) = Utils.packAndGetPackageProperties projectName stdout - .ReplaceEscapedNewLines() .Should() - .Be( + .BeLineEndingEquivalent( """{ "Properties": { "Version": "1.0.0", @@ -165,9 +158,8 @@ type IntegrationTests() = let! struct (stdout, _) = Utils.packAndGetPackageProperties projectName stdout - .ReplaceEscapedNewLines() .Should() - .Be( + .BeLineEndingEquivalent( """{ "Properties": { "Version": "1.0.0", diff --git a/tests/Ionide.KeepAChangelog.Tasks.Test.fsproj b/tests/Ionide.KeepAChangelog.Tasks.Test.fsproj index 311c781..bc17ce0 100644 --- a/tests/Ionide.KeepAChangelog.Tasks.Test.fsproj +++ b/tests/Ionide.KeepAChangelog.Tasks.Test.fsproj @@ -7,6 +7,7 @@ + diff --git a/tests/UnitTests.fs b/tests/UnitTests.fs index b66825b..43a096e 100644 --- a/tests/UnitTests.fs +++ b/tests/UnitTests.fs @@ -1,5 +1,6 @@ module Tests.UnitTests +open Ionide.KeepAChangelog.Tasks.Test open Moq open Microsoft.Build.Framework open Ionide.KeepAChangelog.Tasks @@ -7,6 +8,7 @@ open Faqt open Faqt.Operators open Microsoft.VisualStudio.TestTools.UnitTesting open Workspace +open Helpers type TestContext = { BuildEngine: Mock @@ -121,7 +123,7 @@ type UnitTests() = %myTask.LatestReleaseNotes .Should() - .Be( + .BeLineEndingEquivalent( """### Added - Created the package