{{ message }}
Repository navigation
Fix and revise chunked transfer encoding test - #6273
Merged
chrisd8088 merged 2 commits intoJun 2, 2026
Merged
Conversation
We first introduced support for the use of HTTP/1.1 chunked transfer encoding during Git LFS object uploads in commit 962be37 of PR git-lfs#386, and in that commit we added the original version of what is now the "chunked transfer encoding" test in our "t/t-chunked-transfer-encoding.sh" shell test script. The initial version of that script was located in a directory named "test" and was named "test-chunked-transfer-encoding.sh". Then in PR git-lfs#3125 we renamed the "test" directory to "t", and in commit 219b7e0 of that PR we also renamed all the shell test scripts within the directory to have the prefix "t-" instead of "test-". One unintended consequence of this change, though, was that it would have silently rendered the "chunked transfer encoding" test ineffective, except that for several unrelated reasons the test was already ineffective and had been so since its initial version. We explain the chain of events which led to this state of affairs in more detail below, but the key reason why we have not detected any of these problems until now is that the test is simply a near- duplicate of the generic, example "happy path" test from our "t/t-happy-path.sh" script. As such it relies entirely on our "lfstest-gitserver" utility program to modify its behaviour after recognizing the name of the test's repository, and to cause the test to fail if the Git LFS client does not behave as the server expects. However, the server has never correctly validated that the client will send a Transfer-Encoding header with the value "chunked" when instructed to do so. Because the shell test does not verify this itself, the test has always trivially passed, even as subsequent changes further degraded the implementation of the related code in our server utility. To help resolve these issues and prevent a similar recurrence in the future, we start by replacing the test with two new tests in the "t/t-batch-storage-encoding.sh" script that we added recently in PR git-lfs#6207. The first of these tests, named "batch storage HTTP upload with chunked transfer encoding", creates a single Git LFS object and uploads it to the test server utility, as did the original "chunked transfer encoding" test. Unlike the original test, though, our new test captures the trace log output from the Git LFS client and verifies using the grep(1) command that the headers of the PUT request used to upload the object include a Transfer-Encoding header with the value "chunked". Further, the test also checks that the PUT request's headers did not include a Content-Length header. Because this test independently verifies the presence in the trace log of a Transfer-Encoding header from the object upload PUT request, we are now guaranteed that the test itself will catch any future regression of the Git LFS client's support for chunked transfer encoding, even if our "lfstest-gitserver" program fails to do so. We want to avoid the possibility that our test's "grep" commands could match against the HTTP headers captured in the trace log from the client's Git LFS Batch and Locking API requests, which precede the final object upload PUT request. To exclude these prior requests from the log, we use the awk(1) command to match against the PUT request line, and then only output that line and all subsequent lines into a new "put.log" file. Unlike the legacy "chunked transfer encoding" test, our new test simply checks that its Git LFS object is successfully uploaded, as there is no particular value in then performing a download operation as well. The legacy test only included this extra step because the whole test was copied from the sample "happy path" test without modification, and that test illustrates both upload and download operations. Our second new test, named "batch storage HTTP upload without transfer encoding", performs a similar set of steps as our first new test, but assumes that the client has not been instructed to use chunked transfer encoding. The test therefore checks that the headers of the object upload PUT request do not include a Transfer-Encoding header, but do include a Content-Length header whose value is the size of the object. While this test does not require the "lfstest-gitserver" utility to act differently than normal, our first new test obviously does, as we need the server to instruct the Git LFS client to include a Transfer-Encoding header when uploading objects. As with the original "chunked transfer encoding" test from PR git-lfs#386, we expect the test server utility to change its behaviour when it recognizes the name of the test's repository. Both the utility's lfsBatchHandler() and storageHandler() functions were revised in that PR to call the testingChunkedTransferEncoding() function, which is intended to return "true" if the name of the repository begins with a specific prefix. Since the initial implementation of the "chunked transfer encoding" test, the name of the repository the test creates has been derived from the name of the enclosing shell script. Because the script was named "test/test-chunked-transfer-encoding.sh", the server utility's testingChunkedTransferEncoding() function was written to compare a request's URL path with the prefix "/test-chunked-transfer-encoding", as our shell test suite makes Git LFS Batch API requests whose first URL path component is the name of a repository. This design worked as intended for Batch API requests (at least until the changes from PR git-lfs#3125). The Git LFS client would make a Batch API request to upload a single object, and the server utility would return a JSON response whose "header" element contained a "Transfer-Encoding" key with the value "chunked". The client then uploaded the object using a PUT request with a "Transfer-Encoding: chunked" header. Our "lfstest-gitserver" utility handles PUT requests with its storageHandler() function, and the intent of the changes from 962be37 of PR git-lfs#386 was that this function would call the testingChunkedTransferEncoding() function to determine if the PUT request should include a Transfer-Encoding header or not. If the testingChunkedTransferEncoding() function returned "true", then the storageHandler() function would check for a Transfer-Encoding header with the value "chunked", and if one was not provided by the client, log an error and halt the server. If this occurred, the client would have then reported an error and exited, causing the test to fail. However, even if the Git LFS client did not send a Transfer-Encoding header when instructed to by the Batch API response, the test server utility's storageHandler() function could never have detected this because its call to the testingChunkedTransferEncoding() function always received a return value of "false". The reason the testingChunkedTransferEncoding() function would always return "false" when called by the storageHandler() function is that the URLs generated by the "lfstest-gitserver" utility for individual Git LFS objects always begin with the path prefix "/storage/" rather than a repository name. We do now include the repository name in these requests as the value of a query parameter named "r", but this enhancement was only made subsequent to PR git-lfs#386, specifically in commit 27cf051 of PR git-lfs#463. Even so, the testingChunkedTransferEncoding() function still only checks the initial component of a request's URL path, and so can never return "true" when passed an object upload request. To resolve this long-standing problem, we now revise the testingChunkedTransferEncoding() function so that it requires a repository name as its sole parameter, and then returns "true" if that name matches a specific string (without a leading URL path separator, since repository names are simply directory names). Since the function is called from both the lfsBatchHandler() and storageHandler() functions, we update each of these to pass the appropriate "repo" variable to the testingChunkedTransferEncoding() function. In the case of the lfsBatchHandler() function, the "repo" variable is populated from the URL path of the current request, whereas in the storageHandler() function the variable is set with the value of the "r" query parameter. We also set the repository name created by our new "batch storage HTTP upload with chunked transfer encoding" test to "batch-storage-upload-encoding-chunked", and update the testingChunkedTransferEncoding() function to compare its input parameter against that name. This repository name better reflects both the name of the test and the name of the test's shell script, and as such aligns with the repository names used by the other tests in the same script. We next address another problem with the storageHandler() function and the way it responds if the testingChunkedTransferEncoding() function returns "true". In the original implementation from commit 962be37 of PR git-lfs#386, if the storageHandler() function were to receive a return value of "true" from the testingChunkedTransferEncoding() function, the storageHandler() function would then check that the current request included a Transfer-Encoding header with the value of "chunked". If this value was not found, then the Fatal() function from the Go standard library's "log" package would be called, causing the server to halt, which in turn would cause the client to exit with a non-zero status code and the test to fail. Note, though, that as we described above, this sequence of events could never actually occur because the testingChunkedTransferEncoding() function would always return "false" to the storageHandler() function. When we introduced the debug() function to the "lfstest-gitserver" utility in commit e2046fd of PR git-lfs#1375, we replaced calls to the Fatal() function of the "log" package with calls to the debug() function. Our debug() function does not cause the calling program to halt immediately, unlike the "log" package's Fatal() function. In the other two instances where we replaced the Fatal() function with our own debug() function, this change did not alter the effectiveness of our tests. In these instances, while the server would no longer halt if an unexpected condition occurred, it still proceeded to return a 4xx status code to the Git LFS client, which would in turn cause the currently-executing test to fail. However, the storageHandler() function depended on the "log" package's Fatal() function to cause the server to exit immediately, and did not implement any other indication to the client that because a Transfer-Encoding header was expected but not received, an error condition had occurred. To resolve this problem, we change the storageHandler() function so that if it finds that the Git LFS client has not sent a "Transfer-Encoding: chunked" header when it was instructed to do so, the function now returns a 400 Bad Request HTTP status code to the client. This will cause the client to retry the request, and then after repeatedly receiving 400 status code responses, the client will report an error and exit with a non-zero status, causing the test to fail. We also take the opportunity to simplify the code used by the storageHandler() function to check for a "Transfer-Encoding: chunked" header. Previously, the function had to iterate through the list of values in the TransferEncoding field of the Request structure from the Go standard library's "http" package, checking each value against the string "chunked". We can now replace this explicit loop with a call to the Contains() function of the Go standard library's "slices" package, since that package was added in Go v1.21. Finally, because our new "batch storage HTTP upload with chunked transfer encoding" test creates a repository with the fixed name of "batch-storage-upload-encoding-chunked", and because we have modified the testingChunkedTransferEncoding() function to check for that specific repository name, we have implicitly addressed the third problem with our existing "chunked transfer encoding" test. As noted above, in commit 219b7e0 of PR git-lfs#3125 we renamed all our shell test scripts to begin with "t-" rather than "test-", and an unintended consequence of this change was that the testingChunkedTransferEncoding() function would no longer recognize the repository name from the "chunked transfer encoding" test, even when handling Batch API requests made by the test. As the test was originally written, it passed the name of the script containing the test to the basename(1) command and then used the result as the name of the test's primary Git repository. Since the test's script was initially named "test-chunked-transfer-encoding.sh", the testingChunkedTransferEncoding() function checked for a URL path which began with the prefix "/test-chunked-transfer-encoding" in order to match the name of the test's repository. When the script was renamed to "t-chunked-transfer-encoding.sh" in PR git-lfs#3125, though, the test's repository name automatically changed to "t-chunked-transfer-encoding". As a result, the testingChunkedTransferEncoding() function could no longer recognize the repository name, and so the lfsBatchHandler() function stopped sending Batch API responses with "Transfer-Encoding" keys in the "header" element. Since the test made no independent check that the Git LFS client sent a Transfer-Encoding header, the test still continued to succeed. Fortunately, the legacy "chunked transfer encoding" test was the only test affected in this way by the shell script name changes from PR git-lfs#3125. While the "lfstest-gitserver" utility at that point had several functions similar to the testingChunkedTransferEncoding() function, each of which checked for a specific repository name prefix in the URL path of a request, the tests which relied on these functions to recognize their repositories all used fixed names for their repositories rather than deriving them from the names of the enclosing scripts.
In previous commit in this PR we resolved several problems with our legacy test of the Git LFS client's support for HTTP/1.1 chunked transfer encoding during Git LFS object uploads. One of these problems resulted from a mismatch between the name of the repository created by the previous version of the test and the URL path prefix that was expected by the testingChunkedTransferEncoding() function in our "lfstest-gitserver" test utility program. When the "chunked transfer encoding" test was first introduced, it was located in a shell script named "test-chunked-transfer-encoding.sh" in a directory named "test". The test passed the name of its containing script to the basename(1) command and then used the result as the name of the test's primary Git repository. Our "lfstest-gitserver" utility's testingChunkedTransferEncoding() function then checked for a URL path which began with the prefix "/test-chunked-transfer-encoding" in order to match the name of the test's repository. In PR git-lfs#3125 we renamed the name of our shell test directory from "test" to "t", and in commit 219b7e0 of that PR we renamed all our shell test scripts to begin with "t-" rather than "test-". An unintended consequence of this change, though, was that the testingChunkedTransferEncoding() function would no longer recognize the repository name from the "chunked transfer encoding" test. Due to the design of the shell test, it continued to pass, but without exercising the behaviour of the Git LFS client it was intended to verify. Fortunately, the legacy "chunked transfer encoding" test was the only test affected in this way by the shell script name changes from PR git-lfs#3125. While the "lfstest-gitserver" utility at that point had several functions similar to the testingChunkedTransferEncoding() function, each of which checked for a specific repository name prefix in the URL path of a request, the tests which relied on these functions to recognize their repositories all used fixed names for their repositories rather than deriving them from the names of the enclosing scripts. To help resolve the problems with the "chunked transfer encoding" test, we replaced it with a new "batch storage HTTP upload with chunked transfer encoding" test in our "t/t-batch-storage-encoding.sh" script. This test uses a fixed name for its repository rather than one dynamically determined from the name of the containing script. We then updated the testingChunkedTransferEncoding() function in our server utility to check that the current request's repository name exactly matched that fixed string. We also revised the testingChunkedTransferEncoding() function so that its sole parameter is a repository name string rather than a Request structure from the Go standard library's "http" package. This change resolved another problem with the legacy "chunked transfer encoding" test that was due to the fact that the URL paths of requests to upload individual Git LFS objects do not begin with the repository name as an initial path component, and the original test's implementation did not take account of this. Although the testingChunkedTransferEncoding() function was called while handling an object upload, it could only ever return "false" in this case because the URL path would never begin with a repository name. There are three other functions in our "lfstest-gitserver" utility which were all modelled on the testingChunkedTransferEncoding() function, and as such all check the URL path of a Request structure against a specific prefix. These functions are named testingTusUploadInBatchReq(), testingTusUploadInterruptedInBatchReq(), and testingCustomTransfer(). As we would like to reduce the chance that we might in the future accidentally introduce problems similar to those we found with the "chunked transfer encoding" test, we now revise these three functions so their input parameters are repository names rather than Request structures. The functions are all called from the lfsBatchHandler() function of the server utility, which has already extracted a repository name from the initial path component of the current request, so in each case we can just pass the "repo" variable to the three functions. We also update the repository names used by the associated shell tests to eliminate the legacy "test-" prefix and to align the repository names more closely with the names of the tests as they presently exist. For instance, since PR git-lfs#6207 the tests which rely on the testingTusUploadInBatchReq() function and the testingTusUploadInterruptedInBatchReq() function are now in the "t/t-batch-storage-upload-tus.sh" script, but were originally introduced in a "test/test-resume-tus.sh" script in PR git-lfs#1303. Meanwhile, the tests which rely on the testingCustomTransfer() function are now in the "t/t-custom-transfers.sh" script but were first introduced in a "test/test-custom-transfers.sh" script in PR git-lfs#1367. Finally, we update the testingTusUploadInterruptedInBatchReq() function so it performs a more constrained check of the given repository name, as it only needs to return "true" if the name exactly matches the string "batch-storage-upload-tus-interrupt".
larsxschneider
approved these changes
Jun 2, 2026
Member
There was a problem hiding this comment.
The awk program feels fragile but I am unable to come up with something better.
Member
Author
There was a problem hiding this comment.
I thought about this too, but I think it's OK.
For one thing, we have a positive check in that Transfer-Encoding must appear in the resulting file, so if the awk call started failing to match anything and put.log ended up empty, we'd get a failure and could go fix the test. And if we start matching too early and put.log ends up with multiple requests' worth of trace logs, the negative check for Content-Length would likely start failing because it would find those headers from the Batch and Locking API requests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

This PR replaces the legacy, non-functional test of the Git LFS client's support for chunked transfer encoding during object uploads with a test which verifies that the client correctly sends a
Transfer-Encoding: chunkedheader when instructed to do so by a Git LFS Batch API service, and does not send aContent-Lengthheader at the same time.As well, this PR adds a second test which confirms that the client does not send a
Transfer-Encodingheader when the Batch API service has not requested one.Finally, this test adjusts several functions in our test server utility and also updates the related shell tests to make their repository names more consistent and to help avoid problems similar to the ones which caused our legacy chunked transfer encoding test to be ineffective.
This PR will be most easily reviewed on a commit-by-commit basis.
First Legacy Test Problem
When support for chunked transfer encoding during object uploads was first introduced in PR #386, we added the
chunked transfer encodingtest in what is now ourt/t-chunked-transfer-encoding.shshell script. This test is essentially identical to the generic, samplehappy pathtest in ourt/t-happy-path.shscript, and as such relies on ourlfstest-gitserverutility to detect the name of the test's repository and alter its behaviour to validate that the client sends aTransfer-Encoding: chunkedheader when requested to do so.However, while the Batch API handler in our server utility used to detect the test's repository name correctly, it appears that the object upload handler has never done so, and so the test has been ineffective since it was first written.
Both handlers make calls to the
testingChunkedTransferEncoding()function to check for a special repository name and this function compares the URL path of the current request to the prefix/test-chunked-transfer-encoding. However, only Batch API requests to our test server begin with the repository name as an initial URL path component; the URLs for individual objects begin with/storage/instead.So as the test was originally written, the Batch API handler would have received a
truevalue from thetestingChunkedTransferEncoding()function during the test, and would then have instructed the client to send aTransfer-Encoding: chunkedheader when uploading. The subsequent object upload request, however, would have a URL path which began with/storage/and so the object storage handler would never have checked whether aTransfer-Encodingheader was sent with the object uploadPUTrequest.This problem still persists today, because the URL paths generated by our test server utility for individual object requests still begin with
/storage/, and thetestingChunkedTransferEncoding()function still checks for a prefix of/test-chunked-transfer-encoding. We resolve this issue now by passing just a repository name to thetestingChunkedTransferEncoding()function rather than an URL path, since both the Batch API and object storage handlers have a localrepovariable which they populate with the repository name based on information in the request.Second Legacy Test Problem
The original design of our legacy test expected the storage handler in our test server utility to halt with a fatal error if it found that no
Transfer-Encoding: chunkedheader had been sent when one was expected (although as noted above, the server would never actually perform this check at all).In commit e2046fd of PR #1375 we introduced the
debug()helper function to our server utility and revised many of our calls to the Go standard library'slogpackage to call our new helper function instead. In most cases this was an innocuous change, but in the particular instance of the check in the storage handler for aTransfer-Encodingheader, the server was intended to exit if no header was found when one was expected, with no alternative indication that an error had occurred.The object storage handler in our test server utility still only outputs a message to its debug log if no
Transfer-Encodingheader is found when one is expected, so our new test would never receive any indication of an error condition if the client failed to send aTransfer-Encodingheader unless we resolve this problem. We therefore update the object storage handler so that in such a case, it will now return a 400 Bad Request status code to the client, which would cause the client to retry the request and then ultimately report an error, resulting in a test failure.We also design our new shell test such that it independently verifies that a
Transfer-Encoding: chunkedheader was sent by the Git LFS client in the object uploadPUTrequest, and that noContent-Lengthheader was sent in this request. This guarantees that even we introduce a regression in the test server in the future, the test itself should be able to verify whether the client sendsTransfer-Encoding: chunkedheaders when instructed to do so.Third Legacy Test Problem
As described in #6264 (comment), in PR #3125 we renamed our shell test scripts so all their names now begin with
t-rather thantest-, and in doing so we inadvertently added a third problem which made our legacychunked transfer encodingtest fully ineffective.The
chunked transfer encodingtest derives the name of its primary repository from the name of the enclosing shell script, so the repository name is nowt-chunked-transfer-encoding. Because thetestingChunkedTransferEncoding()function compares the leading portion of the URL path to the prefixtest-chunked-storage-encoding, it currently never returnstrue, even when called from the server utility's Batch API handler.We resolve this issue in our new test by using a fixed string for the name of the repository, and then changing the
testingChunkedTransferEncoding()function to compare the given repository name to exactly that string.We also revise the three other functions in our test server utility that have been modelled on the design of the
testingChunkedTransferEncoding()function. We change each function's input parameter to a simple repository name, and where possible we refine the comparisons performed by the functions. We also take the opportunity to update the names of the repositories created by the related shell tests so they are more consistent with those of the other tests in the same scripts./cc PR #6264