public void ShouldBeAbleToExplicitlyStart() { // Arrange using var arena = new TestArena("-e", "mysql"); // Act arena.WaitForProgramToListen(); var interestingLine = arena.StdOut.FirstOrDefault( line => line.StartsWith( "connection string", StringComparison.InvariantCultureIgnoreCase )); Expect(interestingLine) .Not.To.Be.Null( "TempDb runner should emit the connection string on stdout" ); var connectionString = interestingLine.Split(':') .Skip(1) .JoinWith(":") .Trim(); using (var connection = new MySqlConnection(connectionString)) { // Assert Expect(() => connection.Open()) .Not.To.Throw(); } }
public void ShouldNotReWriteTheFileWhenInHistoryButNotOnDisk() { // Arrange using var arena = new TestArena(); var(source, target) = (arena.SourceFileSystem, arena.TargetFileSystem); var relPath = GetRandomString(2); var data = GetRandomBytes(100); arena.CreateResource( arena.SourcePath, relPath, data); var historyRepo = Substitute.For <ITargetHistoryRepository>(); historyRepo.Exists(relPath) .Returns(true); var targetFilePath = Path.Combine(arena.TargetPath, relPath); var historyItem = new HistoryItem( relPath, data.Length); historyRepo.Find(relPath) .Returns(historyItem); var sut = Create(targetHistoryRepository: historyRepo); // Act sut.Synchronize(source, target); // Assert Expect(targetFilePath) .Not.To.Be.A.File(); }
public void ShouldCopyTheSourceFileToTarget() { // Arrange using var arena = new TestArena(); var(source, target) = (arena.SourceFileSystem, arena.TargetFileSystem); var relPath = "some-file.ext"; var data = GetRandomBytes(); arena.CreateResource( arena.SourcePath, relPath, data); var sut = Create(filters: CreatePermissiveFilter()); // Act sut.Synchronize(source, target); // Assert var inTarget = target.ListResourcesRecursive(); Expect(inTarget) .To.Contain.Only(1) .Item(); var copied = inTarget.Single(); Expect(copied.RelativePath) .To.Equal(relPath); Expect(copied) .To.Have.Data(data); }
public void ShouldNotReWriteTheFile() { // Arrange using var arena = new TestArena(); var(source, target) = (arena.SourceFileSystem, arena.TargetFileSystem); var relPath = GetRandomString(2); var data = GetRandomBytes(100); var targetFilePath = arena.CreateResource( arena.TargetPath, relPath, data); arena.CreateResource( arena.SourcePath, relPath, data); var beforeTest = DateTime.Now; var lastWrite = beforeTest.AddMinutes(GetRandomInt(-100, -1)); File.SetLastWriteTime(targetFilePath, lastWrite); var sut = Create(); // Act sut.Synchronize(source, target); // Assert Expect(targetFilePath) .To.Be.A.File(); var targetInfo = new FileInfo(targetFilePath); Expect(targetInfo.LastWriteTime) .To.Be.Less.Than(beforeTest); }
public void ShouldUpsertHistory() { // Arrange using var arena = new TestArena(); var(source, target) = (arena.SourceFileSystem, arena.TargetFileSystem); var relPath = "some-file.ext"; var data = GetRandomBytes(); arena.CreateResource( arena.SourcePath, relPath, data); var historyRepo = Substitute.For <ITargetHistoryRepository>(); var sut = Create( targetHistoryRepository: historyRepo, filters: CreatePermissiveFilter()); // Act sut.Synchronize(source, target); // Assert Expect(historyRepo) .To.Have.Received(1) .Upsert(Arg.Is <IHistoryItem>( o => o.Path == relPath && o.Size == data.Length )); }
public void ShouldPassThroughAllProvidedIntermediatesInOrder() { // Arrange using var arena = new TestArena(); var resumeStrategy = Substitute.For <IResumeStrategy>(); resumeStrategy.CanResume( Arg.Any <IFileResource>(), Arg.Any <IFileResource>(), Arg.Any <Stream>(), Arg.Any <Stream>()) .Returns(false); var(source, target) = (arena.SourceFileSystem, arena.TargetFileSystem); var relPath = GetRandomString(); var allData = GetRandomBytes(100, 200); // small buffer, likely to be read in one pass arena.CreateResource( arena.SourcePath, relPath, allData); var intermediateCalls = new List <string>(); var endCalls = new List <string>(); var intermediate1 = new GenericPassThrough( (data, count) => intermediateCalls.Add("first"), () => endCalls.Add("first") ); var intermediate2 = new GenericPassThrough( (data, count) => intermediateCalls.Add("second"), () => endCalls.Add("second") ); var intermediate3 = new GenericPassThrough( (data, count) => intermediateCalls.Add("third"), () => endCalls.Add("third") ); var expected = new[] { "first", "second", "third" }; var sut = Create( resumeStrategy, new IPassThrough[] { intermediate1, intermediate2, intermediate3 }, // this test is about notifiers, so we'll // just pretend that all sources are to be required // at the target filters: CreatePermissiveFilter()); // Act sut.Synchronize(source, target); // Assert Expect(intermediateCalls) .To.Equal(expected); Expect(endCalls) .To.Equal(expected); }
public void ShouldResumeWhenResumeStrategySaysYes() { // Arrange using var arena = new TestArena(); var resumeStrategy = Substitute.For <IResumeStrategy>(); resumeStrategy.CanResume( Arg.Any <IFileResource>(), Arg.Any <IFileResource>(), Arg.Any <Stream>(), Arg.Any <Stream>() ) .Returns(true); var(source, target) = (arena.SourceFileSystem, arena.TargetFileSystem); var relPath = GetRandomString(); var allData = GetRandomBytes(1024); var partialSize = GetRandomInt(384, 768); var partialData = allData.Take(partialSize) .ToArray(); var expected = allData.Skip(partialSize) .ToArray(); var targetFilePath = arena.CreateResource( arena.TargetPath, relPath, partialData); arena.CreateResource( arena.SourcePath, relPath, allData); var captured = new List <byte>(); var ended = false; var intermediate = new GenericPassThrough( (data, count) => captured.AddRange(data.Take(count)), () => ended = true ); var sut = Create( resumeStrategy, new IPassThrough[] { intermediate } ); // Act sut.Synchronize(source, target); // Assert var targetData = File.ReadAllBytes(targetFilePath); Expect(targetData) .To.Equal(allData); var transferred = captured.ToArray(); Expect(transferred) .To.Equal(expected, "unexpected transferred data"); Expect(ended) .To.Be.True(); }
public void ShouldRetryOnError() { // Arrange using var arena = new TestArena(); var options = Substitute.For <IOptions>(); options.Retries.Returns(3); using var resetter = new AutoResetter <int>(() => { var original = StreamSource.MaxBuffer; StreamSource.MaxBuffer = 1; return(original); }, original => StreamSource.MaxBuffer = original); var haveErrored = false; var errorPassThrough = new GenericPassThrough( (data, size) => { if (!haveErrored) { haveErrored = true; throw new DirectoryNotFoundException("foo"); } }, () => { } ); // keep the size small since we're forcing a flush at every byte var sourceFile = arena.CreateSourceFile(data: GetRandomBytes(10, 20)); var expected = arena.TargetPathFor(sourceFile); var sut = Create( intermediatePipes: new IPassThrough[] { errorPassThrough }, options: options); // Act sut.Synchronize(arena.SourceFileSystem, arena.TargetFileSystem); // Assert Expect(expected) .To.Exist(); Expect(File.ReadAllBytes(expected)) .To.Equal(sourceFile.Data); }
public void ShouldUpsertHistory() { // Arrange using var arena = new TestArena(); var(source, target) = (arena.SourceFileSystem, arena.TargetFileSystem); var relPath = GetRandomString(2); var data = GetRandomBytes(100); var targetFilePath = arena.CreateResource( arena.TargetPath, relPath, data); arena.CreateResource( arena.SourcePath, relPath, data); var beforeTest = DateTime.Now; var lastWrite = beforeTest.AddMinutes(GetRandomInt(-100, -1)); File.SetLastWriteTime(targetFilePath, lastWrite); var historyRepo = Substitute.For <ITargetHistoryRepository>(); var sut = Create(targetHistoryRepository: historyRepo); // Act sut.Synchronize(source, target); // Assert Expect(historyRepo) .To.Have.Received(1) .Upsert(Arg.Is <IEnumerable <IHistoryItem> >( o => o.Single() .Path == relPath && o.Single() .Size == data.Length )); }
public void ShouldArchiveOnlyThoseFiles() { using var arena = new TestArena(); // Arrange var toArchive = $"{GetRandomFileName()}"; var archiveData = GetRandomBytes(); var sourceFile = arena.CreateSourceFile(toArchive, archiveData); var targetFile = arena.CreateTargetFile(toArchive, archiveData); var targetMarker = arena.CreateTargetFile($"{sourceFile.Name}.t", new byte[0]); var archive = arena.ArchiveFileSystem; var target = arena.TargetFileSystem; var source = arena.SourceFileSystem; var sut = Create(); // Act sut.RunArchiveOperations( target, archive, source); // Assert // archive target should have archived file // -> should not be at target Expect(arena.TargetFileSystem.Exists(toArchive)) .To.Be.False("Target file should not exist any more"); // -> should not be at source either Expect(arena.SourceFileSystem.Exists(toArchive)) .To.Be.False("Source file should not exist any more"); // archive marker should no longer exist Expect(arena.TargetFileSystem.Exists($"{toArchive}.t")) .To.Be.False("Target file marker should not exist any more"); // resource file _should_ exist at archive Expect(arena.ArchiveFileSystem.Exists(toArchive)) .To.Be.True("Resource should be archived"); // marker file _should not_ exist at archive Expect(arena.ArchiveFileSystem.Exists($"{toArchive}.t")) .To.Be.False("Marker should not be present in archive"); }
public void ShouldBeAbleToStopViaStdIn() { // Arrange using (var arena = new TestArena("-e", "localdb")) { // Act arena.WaitForProgramToListen(); var interestingLine = arena.StdOut.FirstOrDefault( line => line.StartsWith( "connection string", StringComparison.InvariantCultureIgnoreCase )); Expect(interestingLine) .Not.To.Be.Null( "TempDb runner should emit the connection string on stdout" ); var connectionString = interestingLine.Split(':') .Skip(1) .JoinWith(":") .Trim(); using (var connection = new SqlConnection(connectionString)) { // Assert Expect(() => connection.Open()) .Not.To.Throw(); } arena.WriteStdIn("stop"); arena.WaitForProgramToExit(); using (var dead = new SqlConnection(connectionString)) { Expect(() => dead.Open()) .To.Throw <SqlException>(); } } }
public void ShouldNotifyNotifiablePipesWithPossibleResumeSizes() { // Arrange using var arena = new TestArena(); var missingData = GetRandomBytes(100); arena.CreateSourceResource( "missing", missingData); var partialFileAllData = "hello world".AsBytes(); var partialTargetData = "hello".AsBytes(); arena.CreateSourceResource("partial", partialFileAllData); arena.CreateTargetResource("partial", partialTargetData); var existingData = GetRandomBytes(100); arena.CreateSourceResource("existing", existingData); arena.CreateTargetResource("existing", existingData); var intermediate1 = new GenericPassThrough( (data, count) => { }, () => { }); var notifiable = new NotifiableGenericPassThrough( (data, count) => { }, () => { }); var options = Substitute.For <IOptions>(); options.ResumeCheckBytes.Returns(512); var sut = Create( new SimpleResumeStrategy(options, Substitute.For <IMessageWriter>()), new IPassThrough[] { notifiable, intermediate1 } .Randomize() .ToArray() ); // Act sut.Synchronize( arena.SourceFileSystem, arena.TargetFileSystem); // Assert var partialResultData = arena.TargetFileSystem.ReadAllBytes( "partial"); Expect(partialResultData) .To.Equal(partialFileAllData, () => $@"Expected { partialFileAllData.Length } bytes, but got { partialResultData.Length }: '{ partialFileAllData.AsString() }' vs '{ partialResultData.AsString() }'"); Expect(arena.TargetFileSystem.ReadAllBytes("existing")) .To.Equal(existingData); Expect(arena.TargetFileSystem.ReadAllBytes("missing")) .To.Equal(missingData); Expect(notifiable.BatchStartedNotifications) .To.Contain.Only(1) .Item(); // should only have one initial notification var initial = notifiable.BatchStartedNotifications.Single(); Expect(initial.resources) .To.Contain .Only(2) .Items(); // should only have partial and missing in sync queue Expect(initial.resources) .To.Contain.Exactly(1) .Matched.By( resource => resource.RelativePath == "missing" ); Expect(initial.resources) .To.Contain.Exactly(1) .Matched.By( resource => resource.RelativePath == "partial" ); var batchComplete = notifiable.BatchCompletedNotifications.Single(); Expect(batchComplete.resources) .To.Contain .Only(2) .Items(); // should only have partial and missing in sync queue Expect(batchComplete.resources) .To.Contain.Exactly(1) .Matched.By( resource => resource.RelativePath == "missing" ); Expect(batchComplete.resources) .To.Contain.Exactly(1) .Matched.By( resource => resource.RelativePath == "partial" ); Expect(notifiable.ResourceNotifications) .To.Contain.Exactly(2) .Items(); var missingResource = notifiable.ResourceNotifications.First(); Expect(missingResource.source.RelativePath) .To.Equal("missing"); Expect(missingResource.source.Size) .To.Equal(missingData.Length); Expect(missingResource.target) .To.Be.Null(); var partialResource = notifiable.ResourceNotifications.Second(); Expect(partialResource.source.RelativePath) .To.Equal("partial"); Expect(partialResource.source.Size) .To.Equal(partialFileAllData.Length); Expect(partialResource.target.RelativePath) .To.Equal("partial"); Expect(partialResource.target.Size) .To.Equal(partialTargetData.Length); Expect(notifiable.CompletedNotifications) .To.Contain.Exactly(2) .Items(); missingResource = notifiable.CompletedNotifications.First(); Expect(missingResource.source.RelativePath) .To.Equal("missing"); Expect(missingResource.source.Size) .To.Equal(missingData.Length); Expect(missingResource.target) .Not.To.Be.Null(); Expect(missingResource.target.RelativePath) .To.Equal("missing"); Expect(missingResource.target.Size) .To.Equal(missingData.Length); partialResource = notifiable.CompletedNotifications.Second(); Expect(partialResource.source.RelativePath) .To.Equal("partial"); Expect(partialResource.source.Size) .To.Equal(partialFileAllData.Length); Expect(partialResource.target.RelativePath) .To.Equal("partial"); Expect(partialResource.target.Size) .To.Equal(partialTargetData.Length); }