Added code to verify what is being retrieved from a repo.

master
Pacman Ghost 2 years ago
parent a68c059aff
commit 98f694537c
  1. 32
      cli/Program.fs
  2. 24
      git-guts/GitRepo.fs
  3. 4
      git-guts/Logs.fs
  4. 87
      git-guts/PackIndex.fs
  5. 66
      git-guts/Utils.fs
  6. 183
      git-guts/Verify.fs
  7. 1
      git-guts/git-guts.fsproj
  8. 2
      git-guts/tests/TestIndex.fs
  9. 5
      git-guts/tests/TestLogs.fs
  10. 6
      git-guts/tests/TestPacks.fs
  11. 6
      git-guts/tests/TestRefs.fs
  12. 49
      git-guts/tests/TestVerify.fs
  13. 53
      git-guts/tests/Utils.fs
  14. 1
      git-guts/tests/tests.fsproj

@ -120,6 +120,30 @@ type DumpLogsCommand() =
dumpLogs settings.RepoDir
0
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
type VerifyObjectsSettings() =
inherit AppSettings()
[<CommandOption( "-p|--progress" )>]
member val Progress = false with get, set
type VerifyObjectsCommand() =
inherit Command<VerifyObjectsSettings>()
override this.Execute( ctx, settings ) =
verifyObjects settings.RepoDir settings.Progress
0
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
type VerifyLogsSettings() =
inherit AppSettings()
type VerifyLogsCommand() =
inherit Command<VerifyLogsSettings>()
override this.Execute( ctx, settings ) =
verifyLogs settings.RepoDir
0
// --------------------------------------------------------------------
[<EntryPoint>]
@ -129,7 +153,7 @@ let main argv =
// colors (see ColorSystemDetector.Detect()), instead of checking if stdout is connected to a terminal,
// so we do that here.
if Console.IsOutputRedirected then
disableSpectreCapabilities
disableSpectreCapabilities ()
// parse the command-line arguments
let app = CommandApp<AppCommand>()
@ -153,5 +177,11 @@ let main argv =
cfg.AddCommand<DumpLogsCommand>( "dump-logs" ).WithDescription(
"Dump logs."
) |> ignore
cfg.AddCommand<VerifyObjectsCommand>( "verify-objects" ).WithDescription(
"Verify retrieval of objects from a git repo."
) |> ignore
cfg.AddCommand<VerifyLogsCommand>( "verify-logs" ).WithDescription(
"Verify logs in a git repo."
) |> ignore
)
app.Run( argv )

@ -2,6 +2,7 @@ namespace git_guts
open System.IO
open System.Text
open System.Text.RegularExpressions
open System.Collections.Generic
// --------------------------------------------------------------------
@ -60,6 +61,29 @@ module GitRepo =
failwith "Object not found."
obj.Value.dumpObj()
let getObjNames repoDir = seq {
// return all loose objects in the repo
let dname = Path.Join( repoDir, ".git/objects" )
let regex = Regex( @"/([0-9a-f]{2})/([0-9a-f]{38}$)" )
for fname in Directory.GetFiles( dname, "*", SearchOption.AllDirectories ) do
let fname2 = fname.Replace( Path.DirectorySeparatorChar, '/' )
let matches = regex.Matches( fname2 )
if matches.Count > 0 then
let groups = matches.[0].Groups
let objName = groups.[1].Value + groups.[2].Value
yield objName, fname2
// return all objects in each pack
for fname in findRepoPacks repoDir do
let mutable objs = [] // FUDGE! Can't yield from inside the onObject callback :-/
_readPackIndexFile fname ( fun fanout -> () ) ( fun objNo nObjs objName crc offset ->
objs <- objs @ [ ( objName, fname ) ]
)
yield! objs
}
let dumpPackFile fname =
// FUDGE! This is a wrapper function that passes in the recursively-called _findRepoObjRec function :-/
_dumpPackFile fname _findRepoObjRec

@ -40,7 +40,7 @@ type LogEntry =
[<AutoOpen>]
module Logs =
let private _findLogFiles repoDir = seq {
let internal _findLogFiles repoDir = seq {
// find log files in the specified repo
let dname = Path.Join( repoDir, "/.git/logs" )
if Directory.Exists( dname ) then
@ -52,7 +52,7 @@ module Logs =
yield ref, fname
}
let private _readLogFile fname = seq {
let internal _readLogFile fname = seq {
let regex = Regex( @"^([0-9a-f]{40}) ([0-9a-f]{40}) (.+?) \<(.+?)\> (\d+) ([+-]\d{4})(\s+[^:]+)?" )
for line in File.ReadLines( fname ) do
let line2 = line.Trim()

@ -99,10 +99,11 @@ module PackIndex =
( objName, crc, offset )
let internal _dumpPackIndexFile fname =
let internal _readPackIndexFile fname onFanoutTable onObject =
// initialize
use inp = new FileStream( fname, FileMode.Open, FileAccess.Read, FileShare.Read )
let fname2 = changeExtn fname ".idx"
use inp = new FileStream( fname2, FileMode.Open, FileAccess.Read, FileShare.Read )
// read the header
let version = _readPackIndexHeader inp
@ -110,44 +111,60 @@ module PackIndex =
// read the fanout table
let getFanoutVals = Seq.initInfinite (fun n -> readNboInt4 inp)
let fanout = Seq.take 256 getFanoutVals |> Seq.toArray
onFanoutTable fanout
let nObjs = fanout.[255]
// dump the fanout table header
AnsiConsole.MarkupLine( "{0}", makeHeader "FANOUT" "" )
printfn ""
let fieldWidth = Math.Max( String.Format( "{0}", nObjs ).Length, 2 )
let fmt = String.Format( "{{0,{0}}}", fieldWidth )
printf " "
for col = 0 to 15 do
let iVal = String.Format( "{0:x2}", col )
printf " %s" ( String.Format( fmt, iVal ) )
printfn ""
printf " "
for col = 0 to 15 do
let ruler = String( '-', fieldWidth )
printf " %s" ruler
printfn ""
// dump the fanout table
for row = 0 to 15 do
printf "%02x:" (16 * row)
for col = 0 to 15 do
let fanoutVal = String.Format( fmt, fanout.[16*row+col] )
printf " %s" fanoutVal
printfn ""
// dump the objects
let fieldWidth2 = String.Format( "{0}", nObjs ).Length
printfn ""
let hdr = sprintf "OBJECTS (%d)" nObjs
AnsiConsole.MarkupLine( "{0}", makeHeader hdr "" )
printfn ""
let prefix = String( ' ', fieldWidth2 )
printfn "%s name crc offset" prefix
printfn "%s ---------------------------------------- -------- --------" prefix
let fmt = sprintf "{0,%d}: {1} {2:x8} 0x{3:x}" fieldWidth2
for objNo = 0 to nObjs-1 do
let objName, crc, offset = readPackIndexObject inp objNo nObjs
onObject objNo nObjs objName crc offset
let internal _dumpPackIndexFile fname =
_readPackIndexFile fname ( fun fanout ->
// dump the fanout table header
AnsiConsole.MarkupLine( "{0}", makeHeader "FANOUT" "" )
printfn ""
let nObjs = fanout.[255]
let fieldWidth = Math.Max( String.Format( "{0}", nObjs ).Length, 2 )
let fmt = String.Format( "{{0,{0}}}", fieldWidth )
printf " "
for col = 0 to 15 do
let iVal = String.Format( "{0:x2}", col )
printf " %s" ( String.Format( fmt, iVal ) )
printfn ""
printf " "
for col = 0 to 15 do
let ruler = String( '-', fieldWidth )
printf " %s" ruler
printfn ""
// dump the fanout table
for row = 0 to 15 do
printf "%02x:" (16 * row)
for col = 0 to 15 do
let fanoutVal = String.Format( fmt, fanout.[16*row+col] )
printf " %s" fanoutVal
printfn ""
) ( fun objNo nObjs objName crc offset ->
let fieldWidth = String.Format( "{0}", nObjs ).Length
if objNo = 0 then
// output the header
printfn ""
let hdr = sprintf "OBJECTS (%d)" nObjs
AnsiConsole.MarkupLine( "{0}", makeHeader hdr "" )
printfn ""
let prefix = String( ' ', fieldWidth )
printfn "%s name crc offset" prefix
printfn "%s ---------------------------------------- -------- --------" prefix
// dump the next object
let fmt = sprintf "{0,%d}: {1} {2:x8} 0x{3:x}" fieldWidth
AnsiConsole.MarkupLine( fmt,
objNo, objNameStr objName, crc, offset
)
)

@ -3,11 +3,36 @@ namespace git_guts
open System
open System.Text
open System.IO
open System.Diagnostics
open Spectre.Console
// --------------------------------------------------------------------
type CaptureStdout () =
// Temporarily capture output sent to stdout.
// set up a buffer to capture stdout
let _writer = new StringWriter()
let _prevOut = System.Console.Out
let _prevAnsiConsole = AnsiConsole.Console.Profile.Out
do
System.Console.SetOut( _writer )
AnsiConsole.Console.Profile.Out <- new AnsiConsoleOutput( _writer )
interface IDisposable with
member this.Dispose() =
// clean up
System.Console.SetOut( _prevOut )
AnsiConsole.Console.Profile.Out <- _prevAnsiConsole
_writer.Dispose()
member this.getOutput =
// return the captured output
_writer.ToString()
// --------------------------------------------------------------------
type StringBuilderBuf () =
// Allow messages to be built up in a StringBuilder.
@ -31,7 +56,31 @@ type StringBuilderBuf () =
[<AutoOpen>]
module Utils =
let disableSpectreCapabilities =
let runGit repoDir cmd args =
// run git and capture the output
let gitPath = "git" // nb: we assume this is on the PATH
let gitDir = Path.Combine( repoDir, ".git" )
let startInfo = ProcessStartInfo( FileName=gitPath, RedirectStandardOutput=true, UseShellExecute=false )
let addArg arg = startInfo.ArgumentList.Add( arg )
Seq.iter addArg [| "--git-dir"; gitDir; cmd |]
Seq.iter addArg args
let proc = Process.Start( startInfo )
let getBytes = Seq.initInfinite ( fun _ -> proc.StandardOutput.BaseStream.ReadByte() )
let output = getBytes |> Seq.takeWhile ( fun b -> b <> -1 ) |> Seq.map ( fun b -> byte(b) ) |> Seq.toArray
proc.WaitForExit()
if proc.ExitCode <> 0 then
failwithf "git failure: rc=%d" proc.ExitCode
output
let runGitText repoDir cmd args =
// run git and capture the output as text
Encoding.UTF8.GetString( runGit repoDir cmd args )
let runGitGc repoDir =
// run git garbage collection
runGit repoDir "gc" [] |> ignore
let disableSpectreCapabilities () =
// disable colors (and other capabilities) in Spectre.Console
AnsiConsole.Profile.Capabilities.ColorSystem <- ColorSystem.NoColors
AnsiConsole.Profile.Capabilities.Ansi <- false
@ -163,3 +212,18 @@ module Utils =
// parse a timestamp
let epoch = DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc )
epoch.AddSeconds( float( tstamp ) )
let plural n val1 val2 =
// return a pluralized string
sprintf "%d %s" n ( if n = 1 then val1 else val2 )
let friendlyByteCount nBytes =
// return a friendly byte-count string
if nBytes < 1024L then
plural (int nBytes) "byte" "bytes"
else if nBytes < 1024L * 1024L then
sprintf "%.1f KB" ( float( nBytes ) / 1024.0 )
else if nBytes < 1024L * 1024L * 1024L then
sprintf "%.1f MB" ( float( nBytes ) / 1024.0 / 1024.0 )
else
sprintf "%.1f GB" ( float( nBytes ) / 1024.0 / 1024.0 / 1024.0 )

@ -0,0 +1,183 @@
namespace git_guts
open System
open System.IO
open System.Text
open System.Text.RegularExpressions
// --------------------------------------------------------------------
[<AutoOpen>]
module VerifyObjects =
let private _adjustGitTreeOutput (objDump: string) =
// adjust git's output for TREE objects so that it matches what we output
let regex = Regex( @"^(\d+) [a-z]+ ([0-9a-f]{40})\s+(.+)$" )
let buf = new StringBuilder()
let reader = new StreamReader( new MemoryStream( Encoding.UTF8.GetBytes( objDump ) ) )
while not reader.EndOfStream do
let line = reader.ReadLine()
let matches = regex.Matches( line )
let groups = matches.[0].Groups
let perms, objName = groups.[1].Value, groups.[2].Value
let mutable path = groups.[3].Value
// FUDGE! git outputs UTF-8 bytes as octal-encoded strings, not the bytes themselves?!
if path.[0] = '"' && path.[path.Length-1] = '"' then
let rec updatePath (partialPath: string) = seq {
let pos = partialPath.IndexOf( '\\' )
if pos < 0 then
yield! Encoding.UTF8.GetBytes( partialPath )
else
yield! Encoding.UTF8.GetBytes( partialPath.Substring( 0, pos ) )
let n = Convert.ToByte( partialPath.Substring( pos+1, 3 ), 8 )
yield byte( n )
yield! updatePath ( partialPath.Substring( pos+4 ) )
}
path <- Encoding.UTF8.GetString(
updatePath ( path.Substring( 1, path.Length-2 ) ) |> Seq.toArray
)
buf.AppendFormat( "{0} {1} {2}\n", perms, objName, path ) |> ignore
buf.ToString().TrimEnd()
let verifyObjects repoDir progress =
// NOTE: This will iterate over every object in a repo, and compare what we retrieve with what
// "git cat-file" returns. In particular, this will include *every* revision of *every* file,
// so for large repo's, it will take some time...
// initialize
disableSpectreCapabilities ()
let mutable currPackFname = ""
let mutable packObjCounts = Map[ ( "", 0 ) ]
let onEndPackFile () =
let nObjs = packObjCounts.[ currPackFname ]
printfn "- Checked %s." ( plural nObjs "object" "objects" )
// check each object in the repo
printfn "Checking loose objects..." // nb: because getObjNames returns loose objects first
for objName, fname in getObjNames repoDir do
// check if we have a loose object or an object in a pack
let packFname =
if Path.GetExtension( fname ) = ".pack" then Path.GetFileName( fname ) else ""
// check if we've started a new pack
if packFname <> currPackFname then
// yup - log the end of the current one
onEndPackFile ()
// prepare to start processing the new pack file
currPackFname <- packFname
packObjCounts <- packObjCounts.Add ( currPackFname, 0 )
let fsize = FileInfo( fname ).Length
printfn ""
printfn "Checking pack file (%s): %s" (friendlyByteCount fsize) currPackFname
// find the next object
if progress then
eprintfn "- Checking object: %s" objName
let objRec = _findRepoObjRec repoDir objName
if objRec.IsNone then
failwithf "Can't find object: %s" objName
let mutable objData = objRec.Value.objData
let obj = makeGitObject objRec.Value
packObjCounts <- packObjCounts.Add ( currPackFname, packObjCounts.[currPackFname]+1 )
if progress then
eprintfn " - Got %s: #bytes=%d" obj.objType objData.Length
// check the object type
let expectedObjType = ( runGitText repoDir "cat-file" [ "-t"; objName ] ).TrimEnd()
if obj.objType <> expectedObjType then
failwithf "Object type mismatch for %s: got \"%s\", expected \"%s\"." objName obj.objType expectedObjType
// check the object data
let mutable expectedObjData = ( runGit repoDir "cat-file" [ "-p"; objName ] )
if obj.objType = "tree" then
objData <- Encoding.UTF8.GetBytes(
using ( new CaptureStdout() ) ( fun cap ->
obj.dumpObj()
cap.getOutput.TrimEnd()
)
)
expectedObjData <- Encoding.UTF8.GetBytes(
_adjustGitTreeOutput ( Encoding.UTF8.GetString expectedObjData )
)
if objData <> expectedObjData then
let dname = Path.GetTempPath()
File.WriteAllBytes( Path.Join( dname, "git-content.expected" ), expectedObjData )
File.WriteAllBytes( Path.Join( dname, "git-content.actual" ), objData )
failwithf "Object data mismatch for %s." objName
onEndPackFile ()
// NOTE: These functions generate object names that are invalid (they contain a non-hex character),
// but they will always compare greater/less than a valid name, based on the first byte, which will
// help test how we use the fanout table, and the binary search through the table of object names.
let makeObjName1 byte0 =
sprintf "%02x%s!" byte0 (String('0',37))
let makeObjName2 byte0 =
sprintf "%02x%sz" byte0 (String('f',37))
// NOTE: Also test with an object name that appears in the middle of the range, for a given first byte.
let makeObjName3 byte0 =
sprintf "%02x%s" byte0 "80808080808080808080808080808080808080"
// verify looking up unknown objects
printfn ""
printfn "Checking unknown objects..."
let mutable nObjs = 0
[| makeObjName1; makeObjName2; makeObjName3 |] |> Seq.iter ( fun makeObjName ->
for byte0 = 0 to 255 do
let objName = makeObjName byte0
let obj = findRepoObject repoDir objName
if obj.IsSome then
failwithf "Unexpectedly found object: %s" objName
nObjs <- nObjs + 1
)
printf "- Checked %s." ( plural nObjs "unknown object" "unknown objects." )
// --------------------------------------------------------------------
[<AutoOpen>]
module VerifyLogs =
let verifyLogs repoDir =
// verify reading each log file
for ref, fname in _findLogFiles repoDir do
printfn "Processing log file: %s" fname
// run git to get the log entries for the current ref
let ref2 =
let ref2 = ref.Replace( Path.DirectorySeparatorChar, '/' )
if ref2.Length >= 12 && ref2.Substring( 0, 11 ) = "refs/heads/" then
ref2.Substring( 11 )
else
ref2
let expected = ( runGitText repoDir "reflog" [| "show"; ref2 |] ).TrimEnd()
// NOTE: git shows just enough of the object names for them to be unique, so we need
// to figure out how much that is, so that we can generate the same output :-/
// We assume the first line is a log entry, that starts with an abbreviated object name.
let objNamePrefixLen = expected.IndexOf( ' ' )
// extract the log entries for the current ref
let buf = new StringBuilder()
let mutable nLogEntries = 0
_readLogFile fname |> Seq.rev |> Seq.iteri ( fun logEntryNo logEntry ->
if logEntry.nextRef.IsSome then
let objNamePrefix = logEntry.nextRef.Value.Substring( 0, objNamePrefixLen )
buf.AppendFormat( "{0} {1}@{{{2}}}: {3}", objNamePrefix, ref2, logEntryNo, logEntry.entryType.Value ) |> ignore
if logEntry.msg.IsSome then
buf.AppendFormat( ": {0}", logEntry.msg.Value ) |> ignore
buf.AppendLine( "" ) |> ignore
nLogEntries <- nLogEntries + 1
)
let output = buf.ToString().TrimEnd()
// compare what we extracted with the git output
if output <> expected then
let dname = Path.GetTempPath()
File.WriteAllText( Path.Join( dname, "git-log.expected" ), expected )
File.WriteAllText( Path.Join( dname, "git-log.actual" ), output )
failwithf "Mismatched output for ref: %s" ref2
printfn "- Checked %s." ( plural nLogEntries "log entry" "log entries" )

@ -17,6 +17,7 @@
<Compile Include="Refs.fs" />
<Compile Include="Logs.fs" />
<Compile Include="GitRepo.fs" />
<Compile Include="Verify.fs" />
</ItemGroup>
<ItemGroup>

@ -14,7 +14,7 @@ type TestStagingIndex () =
[<TestInitialize>]
member this.init () =
// prepare to run a test
disableSpectreCapabilities
disableSpectreCapabilities ()
[<TestMethod>]
member this.TestDumpStagingIndex () =

@ -14,7 +14,7 @@ type TestLogs () =
[<TestInitialize>]
member this.init () =
// prepare to run a test
disableSpectreCapabilities
disableSpectreCapabilities ()
[<TestMethod>]
member this.TestDumpLogs () =
@ -30,8 +30,9 @@ type TestLogs () =
let expectedFname =
let fname = Path.GetFileNameWithoutExtension( zipFname ) + ".logs.txt"
Path.Combine( __SOURCE_DIRECTORY__, "fixtures", fname )
cap.checkOutput expectedFname
checkCapturedOutput cap expectedFname
)
// run the tests
doTest "empty.zip"
doTest "simple.zip"

@ -13,7 +13,7 @@ type TestPacks () =
[<TestInitialize>]
member this.init () =
// prepare to run a test
disableSpectreCapabilities
disableSpectreCapabilities ()
[<TestMethod>]
member this.TestDumpPack () =
@ -35,7 +35,7 @@ type TestPacks () =
let expectedFname =
let fname = Path.GetFileNameWithoutExtension( zipFname ) + ".pack-data.txt"
Path.Combine( __SOURCE_DIRECTORY__, "fixtures", fname )
cap.checkOutput expectedFname
checkCapturedOutput cap expectedFname
)
// dump the pack index file
@ -45,7 +45,7 @@ type TestPacks () =
let expectedFname =
let fname = Path.GetFileNameWithoutExtension( zipFname ) + ".pack-index.txt"
Path.Combine( __SOURCE_DIRECTORY__, "fixtures", fname )
cap.checkOutput expectedFname
checkCapturedOutput cap expectedFname
)
// check that we can find each object name correctly

@ -14,7 +14,7 @@ type TestRefs () =
[<TestInitialize>]
member this.init () =
// prepare to run a test
disableSpectreCapabilities
disableSpectreCapabilities ()
[<TestMethod>]
member this.TestDumpRefs () =
@ -30,7 +30,7 @@ type TestRefs () =
let expectedFname =
let fname = Path.GetFileNameWithoutExtension( zipFname ) + ".refs.txt"
Path.Combine( __SOURCE_DIRECTORY__, "fixtures", fname )
cap.checkOutput expectedFname
checkCapturedOutput cap expectedFname
)
// move the loose objects to a pack, and check again
@ -40,7 +40,7 @@ type TestRefs () =
let expectedFname =
let fname = Path.GetFileNameWithoutExtension( zipFname ) + ".refs-packed.txt"
Path.Combine( __SOURCE_DIRECTORY__, "fixtures", fname )
cap.checkOutput expectedFname
checkCapturedOutput cap expectedFname
)
// run the tests

@ -0,0 +1,49 @@
namespace tests
open System
open System.IO
open Microsoft.VisualStudio.TestTools.UnitTesting
open git_guts
// --------------------------------------------------------------------
[<TestClass>]
type TestVerify () =
[<TestInitialize>]
member this.init () =
// prepare to run a test
disableSpectreCapabilities ()
[<TestMethod>]
member this.TestVerify () =
let doChecks repoDir =
// verify retrieving objects from the repo
using ( new CaptureStdout() ) ( fun cap ->
verifyObjects repoDir false
)
// verify retrieving logs from the repo
using ( new CaptureStdout() ) ( fun cap ->
verifyLogs repoDir
)
let doTest zipFname =
// set up the test repo
use gitTestRepo = new GitTestRepo( zipFname )
// do the checks
doChecks gitTestRepo.repoDir
// run garbage collection, and verify again
runGitGc gitTestRepo.repoDir
doChecks gitTestRepo.repoDir
// run the tests
let dname = Path.Combine( __SOURCE_DIRECTORY__, "fixtures" )
for fname in Directory.GetFiles( dname, "*.zip", SearchOption.AllDirectories ) do
doTest ( Path.GetFileName fname )

@ -4,11 +4,12 @@ open System
open System.Text
open System.IO
open System.IO.Compression
open System.Diagnostics
open Microsoft.VisualStudio.TestTools.UnitTesting
open Spectre.Console
open git_guts
// --------------------------------------------------------------------
type TempDir () =
@ -49,58 +50,16 @@ type GitTestRepo ( zipFname ) =
// --------------------------------------------------------------------
type CaptureStdout () =
// Temporarily capture output sent to stdout.
// set up a buffer to capture stdout
let _writer = new StringWriter()
let _prevOut = System.Console.Out
let _prevAnsiConsole = AnsiConsole.Console.Profile.Out
do
System.Console.SetOut( _writer )
AnsiConsole.Console.Profile.Out <- new AnsiConsoleOutput( _writer )
interface IDisposable with
member this.Dispose() =
// clean up
System.Console.SetOut( _prevOut )
AnsiConsole.Console.Profile.Out <- _prevAnsiConsole
_writer.Dispose()
[<AutoOpen>]
module Utils =
member this.checkOutput fname =
let checkCapturedOutput (cap: CaptureStdout) fname =
// compare the captured output with what's expected
let expected = File.ReadAllText( fname, Encoding.UTF8 )
let output = this.getOutput
let output = cap.getOutput
if output <> expected then
let fname2 = Path.Combine( Path.GetTempPath(), "captured-output.txt" )
File.WriteAllText( fname2, output, Encoding.UTF8 )
Assert.Fail(
sprintf "Captured output`mismatch: %s" ( Path.GetFileName( fname ) )
)
member this.getOutput =
// return the captured output
_writer.ToString()
// --------------------------------------------------------------------
[<AutoOpen>]
module Utils =
let runGit repoDir cmd args =
// run git and capture the output
let gitPath = "git" // nb: we assume this is on the PATH
let gitDir = Path.Combine( repoDir, ".git" )
let startInfo = ProcessStartInfo( FileName=gitPath, RedirectStandardOutput=true, UseShellExecute=false )
let addArg arg = startInfo.ArgumentList.Add( arg )
Seq.iter addArg [| "--git-dir"; gitDir; cmd |]
Seq.iter addArg args
let proc = Process.Start( startInfo )
let output = proc.StandardOutput.ReadToEnd()
proc.WaitForExit()
Assert.AreEqual( 0, proc.ExitCode )
output
let runGitGc repoDir =
// run git garbage collection
runGit repoDir "gc" [] |> ignore

@ -12,6 +12,7 @@
<Compile Include="TestPacks.fs" />
<Compile Include="TestRefs.fs" />
<Compile Include="TestLogs.fs" />
<Compile Include="TestVerify.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

Loading…
Cancel
Save