github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/test/devstack/url_test.go (about) 1 //go:build integration 2 3 package devstack 4 5 import ( 6 "fmt" 7 "net/http" 8 "net/http/httptest" 9 "path" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/filecoin-project/bacalhau/pkg/job" 15 "github.com/filecoin-project/bacalhau/pkg/model" 16 "github.com/filecoin-project/bacalhau/pkg/node" 17 "github.com/filecoin-project/bacalhau/pkg/test/scenario" 18 "github.com/stretchr/testify/require" 19 "github.com/stretchr/testify/suite" 20 ) 21 22 type URLTestSuite struct { 23 scenario.ScenarioRunner 24 } 25 26 func TestURLTests(t *testing.T) { 27 suite.Run(t, new(URLTestSuite)) 28 } 29 30 type URLBasedTestCase struct { 31 file1 string 32 file2 string 33 mount1 string 34 mount2 string 35 files map[string]string 36 } 37 38 func runURLTest( 39 suite *URLTestSuite, 40 handler func(w http.ResponseWriter, r *http.Request), 41 testCase URLBasedTestCase, 42 ) { 43 svr := httptest.NewServer(http.HandlerFunc(handler)) 44 defer svr.Close() 45 46 allContent := testCase.files[fmt.Sprintf("/%s", testCase.file1)] + testCase.files[fmt.Sprintf("/%s", testCase.file2)] 47 48 testScenario := scenario.Scenario{ 49 Stack: &scenario.StackConfig{ 50 ComputeConfig: node.NewComputeConfigWith(node.ComputeConfigParams{ 51 JobSelectionPolicy: model.JobSelectionPolicy{ 52 Locality: model.Anywhere, 53 }, 54 }), 55 }, 56 Inputs: scenario.ManyStores( 57 scenario.URLDownload(svr, testCase.file1, testCase.mount1), 58 scenario.URLDownload(svr, testCase.file2, testCase.mount2), 59 ), 60 ResultsChecker: scenario.ManyChecks( 61 scenario.FileEquals(model.DownloadFilenameStderr, ""), 62 scenario.FileEquals(model.DownloadFilenameStdout, allContent), 63 ), 64 JobCheckers: []job.CheckStatesFunction{ 65 job.WaitForSuccessfulCompletion(), 66 }, 67 Spec: model.Spec{ 68 Engine: model.EngineWasm, 69 Verifier: model.VerifierNoop, 70 Publisher: model.PublisherIpfs, 71 Wasm: model.JobSpecWasm{ 72 EntryPoint: scenario.CatFileToStdout.Spec.Wasm.EntryPoint, 73 EntryModule: scenario.CatFileToStdout.Spec.Wasm.EntryModule, 74 Parameters: []string{ 75 testCase.mount1, 76 testCase.mount2, 77 }, 78 }, 79 }, 80 } 81 82 suite.RunScenario(testScenario) 83 } 84 85 func getSimpleTestCase() URLBasedTestCase { 86 file1 := "hello-cid-1.txt" 87 file2 := "hello-cid-2.txt" 88 return URLBasedTestCase{ 89 file1: file1, 90 file2: file2, 91 mount1: "/inputs-1", 92 mount2: "/inputs-2", 93 files: map[string]string{ 94 fmt.Sprintf("/%s", file1): "Before you marry a person, you should first make them use a computer with slow Internet to see who they really are.\n", 95 fmt.Sprintf("/%s", file2): "I walk around like everything's fine, but deep down, inside my shoe, my sock is sliding off.\n", 96 }, 97 } 98 } 99 100 func (s *URLTestSuite) TestMultipleURLs() { 101 testCase := getSimpleTestCase() 102 handler := func(w http.ResponseWriter, r *http.Request) { 103 content, ok := testCase.files[r.URL.Path] 104 if !ok { 105 w.WriteHeader(http.StatusNotFound) 106 w.Write([]byte("not found")) 107 } else { 108 w.WriteHeader(http.StatusOK) 109 w.Write([]byte(content)) 110 } 111 } 112 runURLTest(s, handler, testCase) 113 } 114 115 // both starts should be before both ends if we are downloading in parallel 116 func (s *URLTestSuite) TestURLsInParallel() { 117 mutex := sync.Mutex{} 118 testCase := getSimpleTestCase() 119 120 accessTimes := map[string]int64{} 121 getAccessTime := func() int64 { 122 return time.Now().UnixNano() / int64(time.Millisecond) 123 } 124 getAccessKey := func(filename, append string) string { 125 return fmt.Sprintf("%s_%s", filename, append) 126 } 127 setAccessTime := func(key string) { 128 mutex.Lock() 129 defer mutex.Unlock() 130 accessTimes[key] = getAccessTime() 131 } 132 133 handler := func(w http.ResponseWriter, r *http.Request) { 134 setAccessTime(getAccessKey(r.URL.Path, "start")) 135 time.Sleep(time.Second * 1) 136 setAccessTime(getAccessKey(r.URL.Path, "end")) 137 content, ok := testCase.files[r.URL.Path] 138 if !ok { 139 w.WriteHeader(http.StatusNotFound) 140 w.Write([]byte("not found")) 141 } else { 142 w.WriteHeader(http.StatusOK) 143 w.Write([]byte(content)) 144 } 145 146 } 147 runURLTest(s, handler, testCase) 148 149 start1, ok := accessTimes["/"+getAccessKey(testCase.file1, "start")] 150 require.True(s.T(), ok) 151 start2, ok := accessTimes["/"+getAccessKey(testCase.file2, "start")] 152 require.True(s.T(), ok) 153 end1, ok := accessTimes["/"+getAccessKey(testCase.file1, "end")] 154 require.True(s.T(), ok) 155 end2, ok := accessTimes["/"+getAccessKey(testCase.file2, "end")] 156 require.True(s.T(), ok) 157 158 require.True(s.T(), start2 < end1, "start 2 should be before end 1") 159 require.True(s.T(), start1 < end2, "start 1 should be before end 2") 160 } 161 162 func (s *URLTestSuite) TestFlakyURLs() { 163 mutex := sync.Mutex{} 164 testCase := getSimpleTestCase() 165 accessCounter := map[string]int{} 166 increaseCounter := func(key string) int { 167 mutex.Lock() 168 defer mutex.Unlock() 169 accessCount, ok := accessCounter[key] 170 if !ok { 171 accessCount = 0 172 } 173 accessCount++ 174 accessCounter[key] = accessCount 175 return accessCount 176 } 177 178 handler := func(w http.ResponseWriter, r *http.Request) { 179 accessCounts := increaseCounter(r.URL.Path) 180 if accessCounts < 3 { 181 w.WriteHeader(http.StatusUnauthorized) 182 w.Write([]byte("not found")) 183 return 184 } 185 content, ok := testCase.files[r.URL.Path] 186 if !ok { 187 w.WriteHeader(http.StatusNotFound) 188 w.Write([]byte("not found")) 189 } else { 190 w.WriteHeader(http.StatusOK) 191 w.Write([]byte(content)) 192 } 193 194 } 195 runURLTest(s, handler, testCase) 196 } 197 198 func (s *URLTestSuite) TestIPFSURLCombo() { 199 ipfsfile := "hello-ipfs.txt" 200 urlfile := "hello-url.txt" 201 ipfsmount := "/inputs-1" 202 urlmount := "/inputs-2" 203 204 URLContent := "Common sense is like deodorant. The people who need it most never use it.\n" 205 IPFSContent := "Truth hurts. Maybe not as much as jumping on a bicycle with a seat missing, but it hurts.\n" 206 207 svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 208 w.WriteHeader(http.StatusOK) 209 w.Write([]byte(URLContent)) 210 })) 211 defer svr.Close() 212 213 testScenario := scenario.Scenario{ 214 Stack: &scenario.StackConfig{ 215 ComputeConfig: node.NewComputeConfigWith(node.ComputeConfigParams{ 216 JobSelectionPolicy: model.JobSelectionPolicy{ 217 Locality: model.Anywhere, 218 }, 219 }), 220 }, 221 Inputs: scenario.ManyStores( 222 scenario.StoredText(IPFSContent, path.Join(ipfsmount, ipfsfile)), 223 scenario.URLDownload(svr, urlfile, urlmount), 224 ), 225 Spec: model.Spec{ 226 Engine: model.EngineWasm, 227 Verifier: model.VerifierNoop, 228 Publisher: model.PublisherIpfs, 229 Wasm: model.JobSpecWasm{ 230 EntryPoint: scenario.CatFileToStdout.Spec.Wasm.EntryPoint, 231 EntryModule: scenario.CatFileToStdout.Spec.Wasm.EntryModule, 232 Parameters: []string{ 233 urlmount, 234 path.Join(ipfsmount, ipfsfile), 235 }, 236 }, 237 }, 238 ResultsChecker: scenario.FileEquals(model.DownloadFilenameStdout, URLContent+IPFSContent), 239 JobCheckers: scenario.WaitUntilSuccessful(1), 240 } 241 242 s.RunScenario(testScenario) 243 }