@@ -1804,66 +1804,137 @@ func TestIngest(t *testing.T) {
1804
1804
})
1805
1805
}
1806
1806
1807
+ // linkAndRemovePredicate is like errorfs.InjectIndex, and injects only on the
1808
+ // set of Link and Remove operations, or the complement of that set. Link and
1809
+ // Remove operation errors are hidden by Ingest.
1810
+ type linkAndRemovePredicate struct {
1811
+ isComplement bool
1812
+ * errorfs.InjectIndex
1813
+ }
1814
+
1815
+ func (p linkAndRemovePredicate ) Evaluate (op errorfs.Op ) bool {
1816
+ isLinkOrRemove := op .Kind == errorfs .OpLink || op .Kind == errorfs .OpRemove
1817
+ if (isLinkOrRemove && p .isComplement ) || (! isLinkOrRemove && ! p .isComplement ) {
1818
+ return false
1819
+ }
1820
+ return p .InjectIndex .Evaluate (op )
1821
+ }
1822
+
1823
+ var _ errorfs.Predicate = & linkAndRemovePredicate {}
1824
+
1807
1825
func TestIngestError (t * testing.T ) {
1808
- for i := int32 (0 ); ; i ++ {
1809
- mem := vfs .NewMem ()
1826
+ for _ , linkAndRemove := range []bool {false , true } {
1827
+ for i := int32 (0 ); ; i ++ {
1828
+ mem := vfs .NewMem ()
1810
1829
1811
- f0 , err := mem .Create ("ext0" , vfs .WriteCategoryUnspecified )
1812
- require .NoError (t , err )
1813
- w := sstable .NewWriter (objstorageprovider .NewFileWritable (f0 ), sstable.WriterOptions {})
1814
- require .NoError (t , w .Set ([]byte ("d" ), nil ))
1815
- require .NoError (t , w .Close ())
1816
- f1 , err := mem .Create ("ext1" , vfs .WriteCategoryUnspecified )
1817
- require .NoError (t , err )
1818
- w = sstable .NewWriter (objstorageprovider .NewFileWritable (f1 ), sstable.WriterOptions {})
1819
- require .NoError (t , w .Set ([]byte ("d" ), nil ))
1820
- require .NoError (t , w .Close ())
1830
+ f0 , err := mem .Create ("ext0" , vfs .WriteCategoryUnspecified )
1831
+ require .NoError (t , err )
1832
+ w := sstable .NewWriter (objstorageprovider .NewFileWritable (f0 ), sstable.WriterOptions {})
1833
+ require .NoError (t , w .Set ([]byte ("d" ), [] byte ( "d1" ) ))
1834
+ require .NoError (t , w .Close ())
1835
+ f1 , err := mem .Create ("ext1" , vfs .WriteCategoryUnspecified )
1836
+ require .NoError (t , err )
1837
+ w = sstable .NewWriter (objstorageprovider .NewFileWritable (f1 ), sstable.WriterOptions {})
1838
+ require .NoError (t , w .Set ([]byte ("d" ), [] byte ( "d2" ) ))
1839
+ require .NoError (t , w .Close ())
1821
1840
1822
- ii := errorfs .OnIndex (- 1 )
1823
- d , err := Open ("" , & Options {
1824
- FS : errorfs .Wrap (mem , errorfs .ErrInjected .If (ii )),
1825
- Logger : panicLogger {},
1826
- L0CompactionThreshold : 8 ,
1827
- })
1828
- require .NoError (t , err )
1829
- // Force the creation of an L0 sstable that overlaps with the tables
1830
- // we'll attempt to ingest. This ensures that we exercise filesystem
1831
- // codepaths when determining the ingest target level.
1832
- require .NoError (t , d .Set ([]byte ("a" ), nil , nil ))
1833
- require .NoError (t , d .Set ([]byte ("d" ), nil , nil ))
1834
- require .NoError (t , d .Flush ())
1835
-
1836
- t .Run (fmt .Sprintf ("index-%d" , i ), func (t * testing.T ) {
1837
- defer func () {
1838
- if r := recover (); r != nil {
1839
- if e , ok := r .(error ); ok && errors .Is (e , errorfs .ErrInjected ) {
1840
- return
1841
+ ii := errorfs .OnIndex (- 1 )
1842
+ d , err := Open ("" , & Options {
1843
+ FS : errorfs .Wrap (
1844
+ mem , errorfs .ErrInjected .If (linkAndRemovePredicate {
1845
+ isComplement : ! linkAndRemove , InjectIndex : ii })),
1846
+ Logger : panicLogger {},
1847
+ L0CompactionThreshold : 8 ,
1848
+ })
1849
+ require .NoError (t , err )
1850
+ // Force the creation of an L0 sstable that overlaps with the tables
1851
+ // we'll attempt to ingest. This ensures that we exercise filesystem
1852
+ // codepaths when determining the ingest target level.
1853
+ require .NoError (t , d .Set ([]byte ("a" ), nil , nil ))
1854
+ require .NoError (t , d .Set ([]byte ("d" ), []byte ("d0" ), nil ))
1855
+ require .NoError (t , d .Flush ())
1856
+
1857
+ checkVal := func (t * testing.T , expected string ) {
1858
+ ii .Store (math .MaxInt32 )
1859
+ val , closer , err := d .Get ([]byte ("d" ))
1860
+ require .NoError (t , err )
1861
+ defer closer .Close ()
1862
+ require .Equal (t , expected , string (val ))
1863
+ }
1864
+ observedPanic := false
1865
+ var errWasInjected bool
1866
+ t .Run (fmt .Sprintf ("link-and-remove=%t,index=%d" , linkAndRemove , i ), func (t * testing.T ) {
1867
+ defer func () {
1868
+ if r := recover (); r != nil {
1869
+ // Error injected on link or remove should be completely hidden,
1870
+ // let alone cause a panic.
1871
+ require .False (t , linkAndRemove )
1872
+ observedPanic = true
1873
+ errWasInjected = ii .Load () < 0
1874
+ t .Logf ("error with panic" )
1875
+ if e , ok := r .(error ); ok && errors .Is (e , errorfs .ErrInjected ) {
1876
+ return
1877
+ }
1878
+ // d.opts.Logger.Fatalf won't propagate ErrInjected
1879
+ // itself, but should contain the error message.
1880
+ if strings .HasSuffix (fmt .Sprint (r ), errorfs .ErrInjected .Error ()) {
1881
+ return
1882
+ }
1883
+ t .Fatal (r )
1841
1884
}
1842
- // d.opts.Logger.Fatalf won't propagate ErrInjected
1843
- // itself, but should contain the error message.
1844
- if strings .HasSuffix (fmt .Sprint (r ), errorfs .ErrInjected .Error ()) {
1845
- return
1885
+ }()
1886
+
1887
+ ii .Store (i )
1888
+ err1 := d .Ingest (context .Background (), []string {"ext0" })
1889
+ err2 := d .Ingest (context .Background (), []string {"ext1" })
1890
+ errWasInjected = ii .Load () < 0
1891
+ // Check that the value of "d" is as expected, given the observed
1892
+ // errors, if any. We only do this if the error did not cause a panic,
1893
+ // since the DB should be usable.
1894
+ expectedVal := "d2"
1895
+ if err2 != nil {
1896
+ if err1 != nil {
1897
+ expectedVal = "d0"
1898
+ } else {
1899
+ expectedVal = "d1"
1846
1900
}
1847
- t .Fatal (r )
1848
1901
}
1849
- }()
1850
1902
1851
- ii .Store (i )
1852
- err1 := d .Ingest (context .Background (), []string {"ext0" })
1853
- err2 := d .Ingest (context .Background (), []string {"ext1" })
1854
- err := firstError (err1 , err2 )
1855
- if err != nil && ! errors .Is (err , errorfs .ErrInjected ) {
1856
- t .Fatal (err )
1857
- }
1858
- })
1903
+ checkVal (t , expectedVal )
1904
+ err := firstError (err1 , err2 )
1905
+ if err != nil {
1906
+ // Error injected on link or remove should be completely hidden.
1907
+ require .False (t , linkAndRemove )
1908
+ if ! errors .Is (err , errorfs .ErrInjected ) {
1909
+ t .Fatal (err )
1910
+ }
1911
+ if ! errWasInjected {
1912
+ t .Fatalf ("error observed but not injected" )
1913
+ }
1914
+ t .Logf ("error without panic" )
1915
+ // Exercise some more operations on the DB, which must succeed. NB: we
1916
+ // have disabled error injection by calling checkVal above.
1917
+ require .NoError (t , d .Set ([]byte ("d" ), []byte ("d3" ), nil ))
1918
+ checkVal (t , "d3" )
1919
+ require .NoError (t , d .Flush ())
1920
+ } else {
1921
+ if errWasInjected && ! linkAndRemove {
1922
+ t .Fatalf ("error injected but not observed" )
1923
+ }
1924
+ }
1925
+ })
1859
1926
1860
- // d.Close may error if we failed to flush the manifest.
1861
- _ = d .Close ()
1927
+ // d.Close may error if we failed to flush the manifest and panicked.
1928
+ ii .Store (math .MaxInt32 )
1929
+ err = d .Close ()
1930
+ if ! observedPanic {
1931
+ require .NoError (t , err )
1932
+ }
1862
1933
1863
- // If the injector's index is non-negative , the i-th filesystem
1864
- // operation was never executed.
1865
- if ii . Load () >= 0 {
1866
- break
1934
+ // If !errWasInjected , the i-th filesystem operation was never executed.
1935
+ if ! errWasInjected {
1936
+ break
1937
+ }
1867
1938
}
1868
1939
}
1869
1940
}
0 commit comments