diff --git a/README.md b/README.md index b37a2bd..227a59d 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ Available options: - [x] With different formats - [x] Add unit tests(HUnit) - [x] Remove "error" on parse issues, replace with "Maybe" - - [ ] Add -Weverything flag + - [x] Adding quickcheck testing(how to model strftime/log file inputs) + - [x] Add -Werror flag - [ ] `fileembed` with actual data + setup of regression tests - [ ] Add Github CI (optional) - [ ] Checkpoint 3: Performance @@ -45,7 +46,6 @@ Available options: - [ ] Use heap for low memory consumption/Use streaming library - [ ] Benchmarks(optional) - [ ] Checkpoint 4: Stretch goals - - [ ] Adding quickcheck testing(how to model strftime/log file inputs) - [ ] Pull in remote files - [ ] Terminal coloring - [ ] Filename prefixing in output diff --git a/loggy.cabal b/loggy.cabal index 08bd62e..6a4a86a 100644 --- a/loggy.cabal +++ b/loggy.cabal @@ -19,6 +19,7 @@ common common-options -Wincomplete-uni-patterns -Wincomplete-record-updates -Wredundant-constraints + -Werror default-language: Haskell2010 library @@ -42,6 +43,8 @@ test-suite loggy-test other-modules: Test.LoggyCore build-depends: loggy , hspec ^>= 2.7.4 + , hspec-hedgehog + , hedgehog ghc-options: -threaded -rtsopts -with-rtsopts=-N diff --git a/test/Test/LoggyCore.hs b/test/Test/LoggyCore.hs index 0589522..3eb799d 100644 --- a/test/Test/LoggyCore.hs +++ b/test/Test/LoggyCore.hs @@ -1,7 +1,14 @@ module Test.LoggyCore (loggycore) where import Test.Hspec (shouldBe, Spec, describe, it) +import Test.Hspec.Hedgehog (hedgehog, (===), forAll, Gen) import Data.Time +import Data.Time.Clock.System +import qualified Hedgehog.Range as Range +import qualified Hedgehog.Gen as Gen +import Data.List (sort) +import Control.Monad (msum) +import Data.Maybe (catMaybes) import LoggyCore @@ -30,14 +37,21 @@ loggycore = describe "LoggyCoreTest" $ do mergeLogLinesTest extractTimestampTest :: Spec -extractTimestampTest = describe "LoggyCoreTest: extractTimestamp" $ do +extractTimestampTest = describe "LoggyCoreTest: extractTimestampTest" $ do it "simpleTimestamp" $ extractTsHelper inputTime `shouldBe` inputDiffTime it "simpleTimestampWithSpaces" $ extractTsHelper (inputTimeWith " ") `shouldBe` inputDiffTime it "simpleTimestampWithExtraChars" $ extractTsHelper (inputTimeWith " random extra chars") `shouldBe` inputDiffTime it "simpleTimestampWithRepeatedTimestamp" $ extractTsHelper (inputTimeWith $ " " ++ inputTime) `shouldBe` inputDiffTime it "invalidLogLine" $ extractTsHelper "invalid input" `shouldBe` Nothing - where - extractTsHelper tsLog = utctDayTime <$> extractTimestamp dateFormat tsLog + it "extractTimestampPropertyTest" $ hedgehog $ do + logText <- forAll $ Gen.string (Range.linear 0 1000) Gen.ascii + unixTimeSeconds <- forAll $ Gen.int64 (Range.linear 0 1000) + let utcTime = systemToUTCTime $ MkSystemTime unixTimeSeconds 0 + let utcTimeStr = formatTime defaultTimeLocale dateFormat utcTime + let logLine = utcTimeStr ++ " " ++ logText + Just utcTime === extractTimestamp dateFormat logLine + where + extractTsHelper tsLog = utctDayTime <$> extractTimestamp dateFormat tsLog mergeLogLinesTest :: Spec mergeLogLinesTest = describe "LoggyCoreTest: mergeLogLines" $ do @@ -64,4 +78,45 @@ mergeLogLinesTest = describe "LoggyCoreTest: mergeLogLines" $ do it "invalidLogLine" $ testMergeLogs dateFormat [[" invalid log line"]] `shouldBe` MkMergeResult [] InvalidLogLine it "invalidLogLines" $ testMergeLogs dateFormat [[" invalid log line", inputTimeWith " from file 1"]] - `shouldBe` MkMergeResult [inputTimeWith " from file 1"] InvalidLogLine \ No newline at end of file + `shouldBe` MkMergeResult [inputTimeWith " from file 1"] InvalidLogLine + it "mergeLogLinesPropertyTest" $ hedgehog $ do + numFiles <- forAll $ Gen.int (Range.linear 1 10) + fileDateFormats <- forAll $ genDateFormats numFiles + logFiles <- forAll $ mapM genFile fileDateFormats + let MkMergeResult mergedLines mergeStatus = mergeLogLines (zip fileDateFormats logFiles) + length mergedLines === sum (map length logFiles) + mergeStatus === NoWarning + let maybeTsPerLogLine = map (dateFormats `extractTimestamp'`) mergedLines + let tsPerLogLine = catMaybes maybeTsPerLogLine + length tsPerLogLine === length mergedLines + isSorted tsPerLogLine === True + where + dateFormats :: [DateFormat] + dateFormats = ["%H:%M:%S", "%H-%M-%S", "%H/%M/%S", "%H.%M.%S"] + genDateFormats ::Int -> Gen [DateFormat] + genDateFormats numFiles = Gen.list (Range.singleton numFiles) (Gen.element dateFormats) + genFile :: DateFormat -> Gen LogFileLines + genFile dFormat = do + numLogLines <- Gen.int (Range.linear 0 100) + logTxtLines <- Gen.list (Range.singleton numLogLines) (Gen.string (Range.linear 0 100) Gen.ascii) + logTsLst <- genSortedDates numLogLines + let logTsFormattedLst = map (formatTime defaultTimeLocale dFormat) logTsLst + let logLines = zipWith (\ts logLine -> ts ++ " " ++ logLine) logTsFormattedLst logTxtLines + return logLines + genSortedDates :: Int -> Gen [UTCTime] + genSortedDates numLines = do + unixTsLst <- Gen.list (Range.singleton numLines) (Gen.int64 (Range.linear 0 1000)) + let sortedUnixTsLst = sort unixTsLst + let utcTimeLst = map (\ts -> systemToUTCTime $ MkSystemTime ts 0) sortedUnixTsLst + return utcTimeLst + extractTimestamp' :: [DateFormat] -> LogLine -> Maybe UTCTime + -- Brute force try all formats till one matches + extractTimestamp' dFs logLine = tsForLogLine + where + tsForLogLine = msum tsPerDateFormat + tsPerDateFormat = map (`extractTimestamp` logLine) dFs + isSorted :: (Ord a) => [a] -> Bool + isSorted [] = True + isSorted [_] = True + isSorted (x:y:xs) = x <= y && isSorted (y:xs) +