Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #29: use "/" in filepaths even under Windows #30

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 105 additions & 55 deletions .github/workflows/binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,99 +11,149 @@ on:
- master
workflow_dispatch:

defaults:
run:
shell: bash

jobs:
build:
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest]
os: [ubuntu-latest, macOS-latest, windows-latest]
stack: ["2.9.3"]
ghc: ["9.2.5"]
# The GHC version has to match the one in the stack.yaml file.
env:
STACK: "stack --system-ghc"

steps:
- name: Get the version
id: get_version
run: 'echo ::set-output name=version::${GITHUB_REF#refs/tags/}'

- uses: actions/checkout@v2
- uses: actions/checkout@v3

- uses: actions/cache@v2
name: Cache ~/.stack
- name: Setup Haskell Stack
uses: haskell/actions/setup@v2
id: setup
with:
path: |
~/.stack
.stack-work
key: "${{ runner.os }}-stack-${{ matrix.stack }}-ghc-${{ matrix.ghc }}-${{ hashFiles('goldplate.cabal', 'stack.yaml', 'stack.yaml.lock') }}"
restore-keys: "${{ runner.os }}-stack-${{ matrix.stack }}-ghc-${{ matrix.ghc }}-"
enable-stack: true
stack-version: ${{ matrix.stack }}
ghc-version: ${{ matrix.ghc }}

- name: Setup Haskell Stack
uses: haskell/actions/setup@v1
- name: Restore cache
uses: actions/cache/restore@v3
id: cache
with:
enable-stack: true
stack-no-global: true
path: ${{ steps.setup.outputs.stack-root }}
key: ${{ runner.os }}-stack-${{ matrix.stack }}-ghc-${{ matrix.ghc }}-${{ hashFiles('goldplate.cabal', 'stack.yaml', 'stack.yaml.lock') }}
restore-keys: ${{ runner.os }}-stack-${{ matrix.stack }}-ghc-${{ matrix.ghc }}-

- name: Build
run: stack build
run: ${STACK} build

- name: Save cache
uses: actions/cache/save@v3
# Reevaluate key.
env:
key: ${{ runner.os }}-stack-${{ matrix.stack }}-ghc-${{ matrix.ghc }}-${{ hashFiles('goldplate.cabal', 'stack.yaml', 'stack.yaml.lock') }}
# Will fail if we already have a cache with this key.
if: ${{ !(steps.cache.outputs.cache-hit && env.key == steps.cache.outputs.cache-matched-key) }}
with:
path: ${{ steps.setup.outputs.stack-root }}
key: ${{ env.key }}
# Should not error.
# continue-on-error: true

- name: Info about command-line tools used in test-suite
run: |
which bash
which cat
which echo
continue-on-error: true

- name: Info about command-line tools used in test-suite (via stack exec)
run: |
${STACK} exec which bash
${STACK} exec which cat
${STACK} exec which echo
continue-on-error: true

- name: Setup MSYS2 (Windows)
if: ${{ runner.os == 'Windows' }}
run: echo "C:\msys64\mingw64\bin;C:\msys64\usr\bin" >> "${GITHUB_PATH}"

- name: Info about command-line tools used in test-suite
run: |
which bash
which cat
which echo
continue-on-error: true

- name: Info about command-line tools used in test-suite (via stack exec)
run: |
${STACK} exec which bash
${STACK} exec which cat
${STACK} exec which echo
continue-on-error: true

- name: Test
run: ${STACK} test --test-arguments --diff

- name: Get goldplate version
run: |
VERSION="$(${STACK} run -- --numeric-version | tail -1)"
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"

- name: Create artifact
if: startsWith(github.ref, 'refs/tags/v')
# if: startsWith(github.ref, 'refs/tags/v')
run: |
PLATFORM=$(uname | tr 'A-Z' 'a-z')
VERSION=${{ steps.get_version.outputs.version }}
cp $(stack path --local-install-root)/bin/goldplate goldplate
tar -czf goldplate.tar.gz goldplate
PLATFORM="$(uname | tr 'A-Z' 'a-z')"
BIN_DIR="$(${STACK} path --local-install-root)/bin"
EXE=$(cd "${BIN_DIR}"; ls goldplate*)
echo "PLATFORM = ${PLATFORM}"
echo "VERSION = ${VERSION}"
echo "BIN_DIR = ${BIN_DIR}"
echo "EXE = ${EXE}"
cp "${BIN_DIR}/${EXE}" .
tar -czf goldplate.tar.gz "${EXE}"
mkdir -p artifacts
mv goldplate.tar.gz artifacts/goldplate-$VERSION-$PLATFORM-x86_64.tar.gz
mv goldplate.tar.gz "artifacts/goldplate-${VERSION}-${PLATFORM}-x86_64.tar.gz"

- uses: actions/upload-artifact@v2
if: startsWith(github.ref, 'refs/tags/v')
- uses: actions/upload-artifact@v3
# if: startsWith(github.ref, 'refs/tags/v')
with:
path: artifacts/*
name: artifacts

outputs:
version: ${{ env.VERSION }}

release:
name: Release
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
# if: startsWith(github.ref, 'refs/tags/v')

steps:
- name: Get the version
id: get_version
run: 'echo ::set-output name=version::${GITHUB_REF#refs/tags/}'
# - name: Get the version
# id: get_version
# run: echo version="${GITHUB_REF#refs/tags/}" >> "${GITHUB_OUTPUT}"

- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v3
with:
name: artifacts

- name: Display structure of downloaded files
run: ls -R

- uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.get_version.outputs.version }}
release_name: ${{ steps.get_version.outputs.version }}

- name: Upload Linux Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./goldplate-${{ steps.get_version.outputs.version }}-linux-x86_64.tar.gz
asset_name: goldplate-${{ steps.get_version.outputs.version }}-linux-x86_64.tar.gz
asset_content_type: application/gzip

- name: Upload MacOS Asset
uses: actions/upload-release-asset@v1
- uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.build.outputs.version }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./goldplate-${{ steps.get_version.outputs.version }}-darwin-x86_64.tar.gz
asset_name: goldplate-${{ steps.get_version.outputs.version }}-darwin-x86_64.tar.gz
asset_content_type: application/zip
token: ${{ secrets.GITHUB_TOKEN }}
files: |
goldplate-${{ env.VERSION }}-linux-x86_64.tar.gz
goldplate-${{ env.VERSION }}-darwin-x86_64.tar.gz
goldplate-${{ env.VERSION }}-windows-x86_64.tar.gz
generate_release_notes: true
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

- 0.3.0 (2023-02-11)
* Use `/` as path separator even under Windows (by Andreas Abel)
* Add option `--numeric-version` (by Andreas Abel)

- 0.2.1.1 (2023-02-26)
* Bump `aeson` dependency upper bound to 2.1.
* Tested with GHC 8.4 - 9.6.1 alpha3.
Expand Down
4 changes: 2 additions & 2 deletions goldplate.cabal
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Name: goldplate
Version: 0.2.1.1
Version: 0.3.0
Synopsis: A lightweight golden test runner
License: Apache-2.0
License-file: LICENSE
Expand Down Expand Up @@ -36,7 +36,7 @@ Source-repository head
Source-repository this
type: git
location: https://github.com/jaspervdj/goldplate.git
tag: v0.2.1.1
tag: v0.3.0

Library
Hs-source-dirs: src
Expand Down
56 changes: 44 additions & 12 deletions src/Goldplate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Data.Algorithm.Diff
import Data.Algorithm.DiffOutput
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Data.Either (fromRight)
import qualified Data.Foldable as F
import Data.Function (on)
import qualified Data.HashMap.Strict as HMS
Expand Down Expand Up @@ -230,7 +231,7 @@ specExecutions specPath spec = do
inputFiles <- Dir.withCurrentDirectory workDirectory $ do
matches <- globCurrentDir glob
length matches `seq` return matches
return (map (Just . FP.normalise) inputFiles)
return (map (Just . normalise) inputFiles)

-- Create an execution for every concrete input.
forM concreteInputFiles $ \mbInputFile -> do
Expand All @@ -257,6 +258,15 @@ specExecutions specPath spec = do
hoistEither :: Either MissingEnvVar a -> IO a
hoistEither = either throwIO return

-- A version of FP.normalise that uses slash as 'pathSeparator' even under Windows.
-- Drops "." directories in the path.
-- Should make test outputs that contain filepaths more portable.
-- Assumes that the argument is a file rather than a directory.
-- This frees us from corner cases such as "." and "./".
normalise :: FilePath -> FilePath
normalise = List.intercalate "/" . dropDots . FP.splitDirectories
where
dropDots = filter ("." /=)

executionHeader :: Execution -> String
executionHeader execution =
Expand Down Expand Up @@ -401,16 +411,15 @@ runAssert env execution@Execution {..} ExecutionResult {..} assert =
checkAgainstFile expectedPath processor actual0 = do
expected <- readFileOrEmpty expectedPath
let !actual1 = postProcess processor actual0
success = actual1 == expected
shouldFix = envFix env && not success

diff :: [Diff [String]] = either (const []) id $ do
expected' <- T.unpack <$> T.decodeUtf8' expected
actual1' <- T.unpack <$> T.decodeUtf8' actual1
return $
getGroupedDiff
(lines expected')
(lines actual1')
-- If we have a UTF8 decoding error, we fall back to Bytestring equality,
-- but have no diff.
(success, diff) = fromRight (actual1 == expected, []) $ do
expected' <- splitLines . T.unpack <$> T.decodeUtf8' expected
actual1' <- splitLines . T.unpack <$> T.decodeUtf8' actual1
return (expected' == actual1', getGroupedDiff expected' actual1')

shouldFix = envFix env && not success

when shouldFix $ B.writeFile expectedPath actual1
pure . makeAssertResult success . concat $
Expand All @@ -426,6 +435,22 @@ runAssert env execution@Execution {..} ExecutionResult {..} assert =
] ++
[ ["fixed " ++ expectedPath] | shouldFix ]

-- | OS-agnostic version of 'lines'.
--
-- From Bryan O' Sullivan's book, "Real World Haskell"
-- https://github.com/cjwirth/real-world-haskell/blob/3733fb501ca98b6beb44b9814b3a4b4a56b1653f/ch04/SplitLines.hs
splitLines :: String -> [String]
splitLines [] = []
splitLines cs =
let (pre, suf) = List.break isLineTerminator cs
in pre : case suf of
('\r':'\n':rest) -> splitLines rest
('\r':rest) -> splitLines rest
('\n':rest) -> splitLines rest
_ -> []

isLineTerminator :: Char -> Bool
isLineTerminator c = c == '\r' || c == '\n'

--------------------------------------------------------------------------------

Expand Down Expand Up @@ -491,7 +516,7 @@ parseOptions = Options
OA.help "Number of worker jobs")

parserInfo :: OA.ParserInfo Options
parserInfo = OA.info (OA.helper <*> versionOption <*> parseOptions) $
parserInfo = OA.info (OA.helper <*> versionOption <*> numericVersionOption <*> parseOptions) $
OA.fullDesc <>
OA.header goldplateVersion
where
Expand All @@ -501,7 +526,14 @@ parserInfo = OA.info (OA.helper <*> versionOption <*> parseOptions) $
OA.help "Show version info" <>
OA.hidden
goldplateVersion :: String
goldplateVersion = "goldplate v" <> showVersion version
goldplateVersion = "goldplate v" <> goldplateNumericVersion

numericVersionOption = OA.infoOption goldplateNumericVersion $
OA.long "numeric-version" <>
OA.help "Show version number" <>
OA.hidden
goldplateNumericVersion :: String
goldplateNumericVersion = showVersion version

--------------------------------------------------------------------------------

Expand Down
9 changes: 7 additions & 2 deletions tests/Tests.hs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import qualified Data.Aeson as A
import Data.ByteString.Char8 ()
import qualified Data.List as List
import Goldplate (Assert)
import System.Environment (getArgs)
import System.Exit (exitWith)
import System.Process (system)

Expand All @@ -19,5 +22,7 @@ testAssertMultipleDiscriminator =

main :: IO ()
main = do
testAssertMultipleDiscriminator
exitWith =<< system ("goldplate tests")
testAssertMultipleDiscriminator
args <- getArgs
exitWith =<< do
system $ unwords $ "goldplate tests" : args
Loading