Merge branch 'js/vsts-ci'
authorJunio C Hamano <gitster@pobox.com>
Thu, 7 Feb 2019 06:05:26 +0000 (22:05 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 7 Feb 2019 06:05:26 +0000 (22:05 -0800)
Prepare to run test suite on Azure Pipeline.

* js/vsts-ci: (22 commits)
test-date: drop unused parameter to getnanos()
ci: parallelize testing on Windows
ci: speed up Windows phase
tests: optionally skip bin-wrappers/
t0061: workaround issues with --with-dashes and RUNTIME_PREFIX
tests: add t/helper/ to the PATH with --with-dashes
mingw: try to work around issues with the test cleanup
tests: include detailed trace logs with --write-junit-xml upon failure
tests: avoid calling Perl just to determine file sizes
README: add a build badge (status of the Azure Pipelines build)
mingw: be more generous when wrapping up the setitimer() emulation
ci: use git-sdk-64-minimal build artifact
ci: add a Windows job to the Azure Pipelines definition
Add a build definition for Azure DevOps
ci/lib.sh: add support for Azure Pipelines
tests: optionally write results as JUnit-style .xml
test-date: add a subcommand to measure times in shell scripts
ci: use a junction on Windows instead of a symlink
ci: inherit --jobs via MAKEFLAGS in run-build-and-tests
ci/lib.sh: encapsulate Travis-specific things
...

30 files changed:
Makefile
README.md
azure-pipelines.yml [new file with mode: 0644]
ci/install-dependencies.sh
ci/lib-travisci.sh [deleted file]
ci/lib.sh [new file with mode: 0755]
ci/make-test-artifacts.sh [new file with mode: 0755]
ci/mount-fileshare.sh [new file with mode: 0755]
ci/print-test-failures.sh
ci/run-build-and-tests.sh
ci/run-linux32-build.sh
ci/run-linux32-docker.sh
ci/run-static-analysis.sh
ci/run-test-slice.sh [new file with mode: 0755]
ci/run-windows-build.sh
ci/test-documentation.sh
compat/mingw.c
t/.gitignore
t/README
t/helper/test-date.c
t/helper/test-path-utils.c
t/helper/test-tool.c
t/helper/test-tool.h
t/helper/test-xml-encode.c [new file with mode: 0644]
t/t0021-conversion.sh
t/t0061-run-command.sh
t/t1050-large.sh
t/t5315-pack-objects-compression.sh
t/t9303-fast-import-compression.sh
t/test-lib.sh
index afa411d727532f95278438851a5ad09292fb7f48..45e6d700a73b52746e54c69c69ca7f6dcd9cb112 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -763,6 +763,7 @@ TEST_BUILTINS_OBJS += test-submodule-config.o
 TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o
 TEST_BUILTINS_OBJS += test-subprocess.o
 TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
+TEST_BUILTINS_OBJS += test-xml-encode.o
 TEST_BUILTINS_OBJS += test-wildmatch.o
 TEST_BUILTINS_OBJS += test-windows-named-pipe.o
 TEST_BUILTINS_OBJS += test-write-cache.o
@@ -2948,6 +2949,16 @@ rpm::
        @false
 .PHONY: rpm
 
+artifacts-tar:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) \
+               GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
+               $(NO_INSTALL) $(MOFILES)
+       $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
+               SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
+       test -n "$(ARTIFACTS_DIRECTORY)"
+       mkdir -p "$(ARTIFACTS_DIRECTORY)"
+       $(TAR) czf "$(ARTIFACTS_DIRECTORY)/artifacts.tar.gz" $^ templates/blt/
+.PHONY: artifacts-tar
+
 htmldocs = git-htmldocs-$(GIT_VERSION)
 manpages = git-manpages-$(GIT_VERSION)
 .PHONY: dist-doc distclean
index f920a42fad3423b0b20639afd765b64ac2750d8a..764c480c66f9822998d6b5dc8d591155b8467c56 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+[![Build Status](https://dev.azure.com/git/git/_apis/build/status/test-git.git)](https://dev.azure.com/git/git/_build/latest?definitionId=2)
+
 Git - fast, scalable, distributed revision control system
 =========================================================
 
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644 (file)
index 0000000..c329b72
--- /dev/null
@@ -0,0 +1,387 @@
+resources:
+- repo: self
+  fetchDepth: 1
+
+jobs:
+- job: windows_build
+  displayName: Windows Build
+  condition: succeeded()
+  pool: Hosted
+  timeoutInMinutes: 240
+  steps:
+  - powershell: |
+      if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
+        net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no
+        cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\
+      }
+    displayName: 'Mount test-cache'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - powershell: |
+      $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds"
+      $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id
+      $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl
+      (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip")
+      Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force
+      Remove-Item git-sdk-64-minimal.zip
+
+      # Let Git ignore the SDK and the test-cache
+      "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude"
+    displayName: 'Download git-sdk-64-minimal'
+  - powershell: |
+      & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+        ci/make-test-artifacts.sh artifacts
+      "@
+      if (!$?) { exit(1) }
+    displayName: Build
+    env:
+      HOME: $(Build.SourcesDirectory)
+      MSYSTEM: MINGW64
+      DEVELOPER: 1
+      NO_PERL: 1
+  - task: PublishPipelineArtifact@0
+    displayName: 'Publish Pipeline Artifact: test artifacts'
+    inputs:
+      artifactName: 'windows-artifacts'
+      targetPath: '$(Build.SourcesDirectory)\artifacts'
+  - task: PublishPipelineArtifact@0
+    displayName: 'Publish Pipeline Artifact: git-sdk-64-minimal'
+    inputs:
+      artifactName: 'git-sdk-64-minimal'
+      targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal'
+  - powershell: |
+      if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
+        cmd /c rmdir "$(Build.SourcesDirectory)\test-cache"
+      }
+    displayName: 'Unmount test-cache'
+    condition: true
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+
+- job: windows_test
+  displayName: Windows Test
+  dependsOn: windows_build
+  condition: succeeded()
+  pool: Hosted
+  timeoutInMinutes: 240
+  strategy:
+    parallel: 10
+  steps:
+  - powershell: |
+      if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
+        net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no
+        cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\
+      }
+    displayName: 'Mount test-cache'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: DownloadPipelineArtifact@0
+    displayName: 'Download Pipeline Artifact: test artifacts'
+    inputs:
+      artifactName: 'windows-artifacts'
+      targetPath: '$(Build.SourcesDirectory)'
+  - task: DownloadPipelineArtifact@0
+    displayName: 'Download Pipeline Artifact: git-sdk-64-minimal'
+    inputs:
+      artifactName: 'git-sdk-64-minimal'
+      targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal'
+  - powershell: |
+      & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+        test -f artifacts.tar.gz || {
+          echo No test artifacts found\; skipping >&2
+          exit 0
+        }
+        tar xf artifacts.tar.gz || exit 1
+
+        # Let Git ignore the SDK and the test-cache
+        printf '%s\n' /git-sdk-64-minimal/ /test-cache/ >>.git/info/exclude
+
+        ci/run-test-slice.sh `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE || {
+          ci/print-test-failures.sh
+          exit 1
+        }
+      "@
+      if (!$?) { exit(1) }
+    displayName: 'Test (parallel)'
+    env:
+      HOME: $(Build.SourcesDirectory)
+      MSYSTEM: MINGW64
+      NO_SVN_TESTS: 1
+      GIT_TEST_SKIP_REBASE_P: 1
+  - powershell: |
+      if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
+        cmd /c rmdir "$(Build.SourcesDirectory)\test-cache"
+      }
+    displayName: 'Unmount test-cache'
+    condition: true
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: PublishTestResults@2
+    displayName: 'Publish Test Results **/TEST-*.xml'
+    inputs:
+      mergeTestResults: true
+      testRunTitle: 'windows'
+      platform: Windows
+      publishRunAttachments: false
+    condition: succeededOrFailed()
+  - task: PublishBuildArtifacts@1
+    displayName: 'Publish trash directories of failed tests'
+    condition: failed()
+    inputs:
+      PathtoPublish: t/failed-test-artifacts
+      ArtifactName: failed-test-artifacts
+
+- job: linux_clang
+  displayName: linux-clang
+  condition: succeeded()
+  pool: Hosted Ubuntu 1604
+  steps:
+  - bash: |
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
+
+       sudo apt-get update &&
+       sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2-bin &&
+
+       export CC=clang || exit 1
+
+       ci/install-dependencies.sh || exit 1
+       ci/run-build-and-tests.sh || {
+           ci/print-test-failures.sh
+           exit 1
+       }
+
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
+    displayName: 'ci/run-build-and-tests.sh'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: PublishTestResults@2
+    displayName: 'Publish Test Results **/TEST-*.xml'
+    inputs:
+      mergeTestResults: true
+      testRunTitle: 'linux-clang'
+      platform: Linux
+      publishRunAttachments: false
+    condition: succeededOrFailed()
+  - task: PublishBuildArtifacts@1
+    displayName: 'Publish trash directories of failed tests'
+    condition: failed()
+    inputs:
+      PathtoPublish: t/failed-test-artifacts
+      ArtifactName: failed-test-artifacts
+
+- job: linux_gcc
+  displayName: linux-gcc
+  condition: succeeded()
+  pool: Hosted Ubuntu 1604
+  steps:
+  - bash: |
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
+
+       sudo add-apt-repository ppa:ubuntu-toolchain-r/test &&
+       sudo apt-get update &&
+       sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2 language-pack-is git-svn gcc-8 || exit 1
+
+       ci/install-dependencies.sh || exit 1
+       ci/run-build-and-tests.sh || {
+           ci/print-test-failures.sh
+           exit 1
+       }
+
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
+    displayName: 'ci/run-build-and-tests.sh'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: PublishTestResults@2
+    displayName: 'Publish Test Results **/TEST-*.xml'
+    inputs:
+      mergeTestResults: true
+      testRunTitle: 'linux-gcc'
+      platform: Linux
+      publishRunAttachments: false
+    condition: succeededOrFailed()
+  - task: PublishBuildArtifacts@1
+    displayName: 'Publish trash directories of failed tests'
+    condition: failed()
+    inputs:
+      PathtoPublish: t/failed-test-artifacts
+      ArtifactName: failed-test-artifacts
+
+- job: osx_clang
+  displayName: osx-clang
+  condition: succeeded()
+  pool: Hosted macOS
+  steps:
+  - bash: |
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
+
+       export CC=clang
+
+       ci/install-dependencies.sh || exit 1
+       ci/run-build-and-tests.sh || {
+           ci/print-test-failures.sh
+           exit 1
+       }
+
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1
+    displayName: 'ci/run-build-and-tests.sh'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: PublishTestResults@2
+    displayName: 'Publish Test Results **/TEST-*.xml'
+    inputs:
+      mergeTestResults: true
+      testRunTitle: 'osx-clang'
+      platform: macOS
+      publishRunAttachments: false
+    condition: succeededOrFailed()
+  - task: PublishBuildArtifacts@1
+    displayName: 'Publish trash directories of failed tests'
+    condition: failed()
+    inputs:
+      PathtoPublish: t/failed-test-artifacts
+      ArtifactName: failed-test-artifacts
+
+- job: osx_gcc
+  displayName: osx-gcc
+  condition: succeeded()
+  pool: Hosted macOS
+  steps:
+  - bash: |
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
+
+       ci/install-dependencies.sh || exit 1
+       ci/run-build-and-tests.sh || {
+           ci/print-test-failures.sh
+           exit 1
+       }
+
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1
+    displayName: 'ci/run-build-and-tests.sh'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: PublishTestResults@2
+    displayName: 'Publish Test Results **/TEST-*.xml'
+    inputs:
+      mergeTestResults: true
+      testRunTitle: 'osx-gcc'
+      platform: macOS
+      publishRunAttachments: false
+    condition: succeededOrFailed()
+  - task: PublishBuildArtifacts@1
+    displayName: 'Publish trash directories of failed tests'
+    condition: failed()
+    inputs:
+      PathtoPublish: t/failed-test-artifacts
+      ArtifactName: failed-test-artifacts
+
+- job: gettext_poison
+  displayName: GETTEXT_POISON
+  condition: succeeded()
+  pool: Hosted Ubuntu 1604
+  steps:
+  - bash: |
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
+
+       sudo apt-get update &&
+       sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev &&
+
+       export jobname=GETTEXT_POISON || exit 1
+
+       ci/run-build-and-tests.sh || {
+           ci/print-test-failures.sh
+           exit 1
+       }
+
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
+    displayName: 'ci/run-build-and-tests.sh'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: PublishTestResults@2
+    displayName: 'Publish Test Results **/TEST-*.xml'
+    inputs:
+      mergeTestResults: true
+      testRunTitle: 'gettext-poison'
+      platform: Linux
+      publishRunAttachments: false
+    condition: succeededOrFailed()
+  - task: PublishBuildArtifacts@1
+    displayName: 'Publish trash directories of failed tests'
+    condition: failed()
+    inputs:
+      PathtoPublish: t/failed-test-artifacts
+      ArtifactName: failed-test-artifacts
+
+- job: linux32
+  displayName: Linux32
+  condition: succeeded()
+  pool: Hosted Ubuntu 1604
+  steps:
+  - bash: |
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
+
+       res=0
+       sudo AGENT_OS="$AGENT_OS" BUILD_BUILDNUMBER="$BUILD_BUILDNUMBER" BUILD_REPOSITORY_URI="$BUILD_REPOSITORY_URI" BUILD_SOURCEBRANCH="$BUILD_SOURCEBRANCH" BUILD_SOURCEVERSION="$BUILD_SOURCEVERSION" SYSTEM_PHASENAME="$SYSTEM_PHASENAME" SYSTEM_TASKDEFINITIONSURI="$SYSTEM_TASKDEFINITIONSURI" SYSTEM_TEAMPROJECT="$SYSTEM_TEAMPROJECT" CC=$CC MAKEFLAGS="$MAKEFLAGS" bash -lxc ci/run-linux32-docker.sh || res=1
+
+       sudo chmod a+r t/out/TEST-*.xml
+       test ! -d t/failed-test-artifacts || sudo chmod a+r t/failed-test-artifacts
+
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || res=1
+       exit $res
+    displayName: 'ci/run-linux32-docker.sh'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: PublishTestResults@2
+    displayName: 'Publish Test Results **/TEST-*.xml'
+    inputs:
+      mergeTestResults: true
+      testRunTitle: 'linux32'
+      platform: Linux
+      publishRunAttachments: false
+    condition: succeededOrFailed()
+  - task: PublishBuildArtifacts@1
+    displayName: 'Publish trash directories of failed tests'
+    condition: failed()
+    inputs:
+      PathtoPublish: t/failed-test-artifacts
+      ArtifactName: failed-test-artifacts
+
+- job: static_analysis
+  displayName: StaticAnalysis
+  condition: succeeded()
+  pool: Hosted Ubuntu 1604
+  steps:
+  - bash: |
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
+
+       sudo apt-get update &&
+       sudo apt-get install -y coccinelle &&
+
+       export jobname=StaticAnalysis &&
+
+       ci/run-static-analysis.sh || exit 1
+
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
+    displayName: 'ci/run-static-analysis.sh'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+
+- job: documentation
+  displayName: Documentation
+  condition: succeeded()
+  pool: Hosted Ubuntu 1604
+  steps:
+  - bash: |
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
+
+       sudo apt-get update &&
+       sudo apt-get install -y asciidoc xmlto asciidoctor &&
+
+       export ALREADY_HAVE_ASCIIDOCTOR=yes. &&
+       export jobname=Documentation &&
+
+       ci/test-documentation.sh || exit 1
+
+       test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
+    displayName: 'ci/test-documentation.sh'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
index dc719876bb12309c086952d275085d8d3d0690e6..608ff964dee0c15c2c2a97c15fc5a86d5138488a 100755 (executable)
@@ -3,7 +3,7 @@
 # Install dependencies required to build and test Git on Linux and macOS
 #
 
-. ${0%/*}/lib-travisci.sh
+. ${0%/*}/lib.sh
 
 P4WHENCE=http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION
 LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION
@@ -37,7 +37,8 @@ osx-clang|osx-gcc)
        brew update --quiet
        # Uncomment this if you want to run perf tests:
        # brew install gnu-time
-       brew install git-lfs gettext
+       test -z "$BREW_INSTALL_PACKAGES" ||
+       brew install $BREW_INSTALL_PACKAGES
        brew link --force gettext
        brew install caskroom/cask/perforce
        case "$jobname" in
diff --git a/ci/lib-travisci.sh b/ci/lib-travisci.sh
deleted file mode 100755 (executable)
index a479613..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-# Library of functions shared by all CI scripts
-
-skip_branch_tip_with_tag () {
-       # Sometimes, a branch is pushed at the same time the tag that points
-       # at the same commit as the tip of the branch is pushed, and building
-       # both at the same time is a waste.
-       #
-       # Travis gives a tagname e.g. v2.14.0 in $TRAVIS_BRANCH when
-       # the build is triggered by a push to a tag.  Let's see if
-       # $TRAVIS_BRANCH is exactly at a tag, and if so, if it is
-       # different from $TRAVIS_BRANCH.  That way, we can tell if
-       # we are building the tip of a branch that is tagged and
-       # we can skip the build because we won't be skipping a build
-       # of a tag.
-
-       if TAG=$(git describe --exact-match "$TRAVIS_BRANCH" 2>/dev/null) &&
-               test "$TAG" != "$TRAVIS_BRANCH"
-       then
-               echo "$(tput setaf 2)Tip of $TRAVIS_BRANCH is exactly at $TAG$(tput sgr0)"
-               exit 0
-       fi
-}
-
-# Save some info about the current commit's tree, so we can skip the build
-# job if we encounter the same tree again and can provide a useful info
-# message.
-save_good_tree () {
-       echo "$(git rev-parse $TRAVIS_COMMIT^{tree}) $TRAVIS_COMMIT $TRAVIS_JOB_NUMBER $TRAVIS_JOB_ID" >>"$good_trees_file"
-       # limit the file size
-       tail -1000 "$good_trees_file" >"$good_trees_file".tmp
-       mv "$good_trees_file".tmp "$good_trees_file"
-}
-
-# Skip the build job if the same tree has already been built and tested
-# successfully before (e.g. because the branch got rebased, changing only
-# the commit messages).
-skip_good_tree () {
-       if ! good_tree_info="$(grep "^$(git rev-parse $TRAVIS_COMMIT^{tree}) " "$good_trees_file")"
-       then
-               # Haven't seen this tree yet, or no cached good trees file yet.
-               # Continue the build job.
-               return
-       fi
-
-       echo "$good_tree_info" | {
-               read tree prev_good_commit prev_good_job_number prev_good_job_id
-
-               if test "$TRAVIS_JOB_ID" = "$prev_good_job_id"
-               then
-                       cat <<-EOF
-                       $(tput setaf 2)Skipping build job for commit $TRAVIS_COMMIT.$(tput sgr0)
-                       This commit has already been built and tested successfully by this build job.
-                       To force a re-build delete the branch's cache and then hit 'Restart job'.
-                       EOF
-               else
-                       cat <<-EOF
-                       $(tput setaf 2)Skipping build job for commit $TRAVIS_COMMIT.$(tput sgr0)
-                       This commit's tree has already been built and tested successfully in build job $prev_good_job_number for commit $prev_good_commit.
-                       The log of that build job is available at https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$prev_good_job_id
-                       To force a re-build delete the branch's cache and then hit 'Restart job'.
-                       EOF
-               fi
-       }
-
-       exit 0
-}
-
-check_unignored_build_artifacts ()
-{
-       ! git ls-files --other --exclude-standard --error-unmatch \
-               -- ':/*' 2>/dev/null ||
-       {
-               echo "$(tput setaf 1)error: found unignored build artifacts$(tput sgr0)"
-               false
-       }
-}
-
-# Set 'exit on error' for all CI scripts to let the caller know that
-# something went wrong.
-# Set tracing executed commands, primarily setting environment variables
-# and installing dependencies.
-set -ex
-
-cache_dir="$HOME/travis-cache"
-good_trees_file="$cache_dir/good-trees"
-
-mkdir -p "$cache_dir"
-
-skip_branch_tip_with_tag
-skip_good_tree
-
-if test -z "$jobname"
-then
-       jobname="$TRAVIS_OS_NAME-$CC"
-fi
-
-export DEVELOPER=1
-export DEFAULT_TEST_TARGET=prove
-export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save"
-export GIT_TEST_OPTS="--verbose-log -x --immediate"
-export GIT_TEST_CLONE_2GB=YesPlease
-
-case "$jobname" in
-linux-clang|linux-gcc)
-       if [ "$jobname" = linux-gcc ]
-       then
-               export CC=gcc-8
-       fi
-
-       export GIT_TEST_HTTPD=YesPlease
-
-       # The Linux build installs the defined dependency versions below.
-       # The OS X build installs the latest available versions. Keep that
-       # in mind when you encounter a broken OS X build!
-       export LINUX_P4_VERSION="16.2"
-       export LINUX_GIT_LFS_VERSION="1.5.2"
-
-       P4_PATH="$HOME/custom/p4"
-       GIT_LFS_PATH="$HOME/custom/git-lfs"
-       export PATH="$GIT_LFS_PATH:$P4_PATH:$PATH"
-       ;;
-osx-clang|osx-gcc)
-       if [ "$jobname" = osx-gcc ]
-       then
-               export CC=gcc-8
-       fi
-
-       # t9810 occasionally fails on Travis CI OS X
-       # t9816 occasionally fails with "TAP out of sequence errors" on
-       # Travis CI OS X
-       export GIT_SKIP_TESTS="t9810 t9816"
-       ;;
-GIT_TEST_GETTEXT_POISON)
-       export GIT_TEST_GETTEXT_POISON=YesPlease
-       ;;
-esac
-
-export MAKEFLAGS="CC=${CC:-cc}"
diff --git a/ci/lib.sh b/ci/lib.sh
new file mode 100755 (executable)
index 0000000..16f4ecb
--- /dev/null
+++ b/ci/lib.sh
@@ -0,0 +1,188 @@
+# Library of functions shared by all CI scripts
+
+skip_branch_tip_with_tag () {
+       # Sometimes, a branch is pushed at the same time the tag that points
+       # at the same commit as the tip of the branch is pushed, and building
+       # both at the same time is a waste.
+       #
+       # When the build is triggered by a push to a tag, $CI_BRANCH will
+       # have that tagname, e.g. v2.14.0.  Let's see if $CI_BRANCH is
+       # exactly at a tag, and if so, if it is different from $CI_BRANCH.
+       # That way, we can tell if we are building the tip of a branch that
+       # is tagged and we can skip the build because we won't be skipping a
+       # build of a tag.
+
+       if TAG=$(git describe --exact-match "$CI_BRANCH" 2>/dev/null) &&
+               test "$TAG" != "$CI_BRANCH"
+       then
+               echo "$(tput setaf 2)Tip of $CI_BRANCH is exactly at $TAG$(tput sgr0)"
+               exit 0
+       fi
+}
+
+# Save some info about the current commit's tree, so we can skip the build
+# job if we encounter the same tree again and can provide a useful info
+# message.
+save_good_tree () {
+       echo "$(git rev-parse $CI_COMMIT^{tree}) $CI_COMMIT $CI_JOB_NUMBER $CI_JOB_ID" >>"$good_trees_file"
+       # limit the file size
+       tail -1000 "$good_trees_file" >"$good_trees_file".tmp
+       mv "$good_trees_file".tmp "$good_trees_file"
+}
+
+# Skip the build job if the same tree has already been built and tested
+# successfully before (e.g. because the branch got rebased, changing only
+# the commit messages).
+skip_good_tree () {
+       if ! good_tree_info="$(grep "^$(git rev-parse $CI_COMMIT^{tree}) " "$good_trees_file")"
+       then
+               # Haven't seen this tree yet, or no cached good trees file yet.
+               # Continue the build job.
+               return
+       fi
+
+       echo "$good_tree_info" | {
+               read tree prev_good_commit prev_good_job_number prev_good_job_id
+
+               if test "$CI_JOB_ID" = "$prev_good_job_id"
+               then
+                       cat <<-EOF
+                       $(tput setaf 2)Skipping build job for commit $CI_COMMIT.$(tput sgr0)
+                       This commit has already been built and tested successfully by this build job.
+                       To force a re-build delete the branch's cache and then hit 'Restart job'.
+                       EOF
+               else
+                       cat <<-EOF
+                       $(tput setaf 2)Skipping build job for commit $CI_COMMIT.$(tput sgr0)
+                       This commit's tree has already been built and tested successfully in build job $prev_good_job_number for commit $prev_good_commit.
+                       The log of that build job is available at $(url_for_job_id $prev_good_job_id)
+                       To force a re-build delete the branch's cache and then hit 'Restart job'.
+                       EOF
+               fi
+       }
+
+       exit 0
+}
+
+check_unignored_build_artifacts ()
+{
+       ! git ls-files --other --exclude-standard --error-unmatch \
+               -- ':/*' 2>/dev/null ||
+       {
+               echo "$(tput setaf 1)error: found unignored build artifacts$(tput sgr0)"
+               false
+       }
+}
+
+# Set 'exit on error' for all CI scripts to let the caller know that
+# something went wrong.
+# Set tracing executed commands, primarily setting environment variables
+# and installing dependencies.
+set -ex
+
+if test true = "$TRAVIS"
+then
+       CI_TYPE=travis
+       # When building a PR, TRAVIS_BRANCH refers to the *target* branch. Not
+       # what we want here. We want the source branch instead.
+       CI_BRANCH="${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH}"
+       CI_COMMIT="$TRAVIS_COMMIT"
+       CI_JOB_ID="$TRAVIS_JOB_ID"
+       CI_JOB_NUMBER="$TRAVIS_JOB_NUMBER"
+       CI_OS_NAME="$TRAVIS_OS_NAME"
+       CI_REPO_SLUG="$TRAVIS_REPO_SLUG"
+
+       cache_dir="$HOME/travis-cache"
+
+       url_for_job_id () {
+               echo "https://travis-ci.org/$CI_REPO_SLUG/jobs/$1"
+       }
+
+       BREW_INSTALL_PACKAGES="git-lfs gettext"
+       export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save"
+       export GIT_TEST_OPTS="--verbose-log -x --immediate"
+       export MAKEFLAGS="--jobs=2"
+elif test -n "$SYSTEM_COLLECTIONURI" || test -n "$SYSTEM_TASKDEFINITIONSURI"
+then
+       CI_TYPE=azure-pipelines
+       # We are running in Azure Pipelines
+       CI_BRANCH="$BUILD_SOURCEBRANCH"
+       CI_COMMIT="$BUILD_SOURCEVERSION"
+       CI_JOB_ID="$BUILD_BUILDID"
+       CI_JOB_NUMBER="$BUILD_BUILDNUMBER"
+       CI_OS_NAME="$(echo "$AGENT_OS" | tr A-Z a-z)"
+       test darwin != "$CI_OS_NAME" || CI_OS_NAME=osx
+       CI_REPO_SLUG="$(expr "$BUILD_REPOSITORY_URI" : '.*/\([^/]*/[^/]*\)$')"
+       CC="${CC:-gcc}"
+
+       # use a subdirectory of the cache dir (because the file share is shared
+       # among *all* phases)
+       cache_dir="$HOME/test-cache/$SYSTEM_PHASENAME"
+
+       url_for_job_id () {
+               echo "$SYSTEM_TASKDEFINITIONSURI$SYSTEM_TEAMPROJECT/_build/results?buildId=$1"
+       }
+
+       BREW_INSTALL_PACKAGES=gcc@8
+       export GIT_PROVE_OPTS="--timer --jobs 10 --state=failed,slow,save"
+       export GIT_TEST_OPTS="--verbose-log -x --write-junit-xml"
+       export MAKEFLAGS="--jobs=10"
+       test windows_nt != "$CI_OS_NAME" ||
+       GIT_TEST_OPTS="--no-chain-lint --no-bin-wrappers $GIT_TEST_OPTS"
+else
+       echo "Could not identify CI type" >&2
+       exit 1
+fi
+
+good_trees_file="$cache_dir/good-trees"
+
+mkdir -p "$cache_dir"
+
+skip_branch_tip_with_tag
+skip_good_tree
+
+if test -z "$jobname"
+then
+       jobname="$CI_OS_NAME-$CC"
+fi
+
+export DEVELOPER=1
+export DEFAULT_TEST_TARGET=prove
+export GIT_TEST_CLONE_2GB=YesPlease
+
+case "$jobname" in
+linux-clang|linux-gcc)
+       if [ "$jobname" = linux-gcc ]
+       then
+               export CC=gcc-8
+       fi
+
+       export GIT_TEST_HTTPD=YesPlease
+
+       # The Linux build installs the defined dependency versions below.
+       # The OS X build installs the latest available versions. Keep that
+       # in mind when you encounter a broken OS X build!
+       export LINUX_P4_VERSION="16.2"
+       export LINUX_GIT_LFS_VERSION="1.5.2"
+
+       P4_PATH="$HOME/custom/p4"
+       GIT_LFS_PATH="$HOME/custom/git-lfs"
+       export PATH="$GIT_LFS_PATH:$P4_PATH:$PATH"
+       ;;
+osx-clang|osx-gcc)
+       if [ "$jobname" = osx-gcc ]
+       then
+               export CC=gcc-8
+       fi
+
+       # t9810 occasionally fails on Travis CI OS X
+       # t9816 occasionally fails with "TAP out of sequence errors" on
+       # Travis CI OS X
+       export GIT_SKIP_TESTS="t9810 t9816"
+       ;;
+GIT_TEST_GETTEXT_POISON)
+       export GIT_TEST_GETTEXT_POISON=YesPlease
+       ;;
+esac
+
+export MAKEFLAGS="CC=${CC:-cc}"
diff --git a/ci/make-test-artifacts.sh b/ci/make-test-artifacts.sh
new file mode 100755 (executable)
index 0000000..6469674
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# Build Git and store artifacts for testing
+#
+
+mkdir -p "$1" # in case ci/lib.sh decides to quit early
+
+. ${0%/*}/lib.sh
+
+make artifacts-tar ARTIFACTS_DIRECTORY="$1"
+
+check_unignored_build_artifacts
diff --git a/ci/mount-fileshare.sh b/ci/mount-fileshare.sh
new file mode 100755 (executable)
index 0000000..26b58a8
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+die () {
+       echo "$*" >&2
+       exit 1
+}
+
+test $# = 4 ||
+die "Usage: $0 <share> <username> <password> <mountpoint>"
+
+mkdir -p "$4" || die "Could not create $4"
+
+case "$(uname -s)" in
+Linux)
+       sudo mount -t cifs -o vers=3.0,username="$2",password="$3",dir_mode=0777,file_mode=0777,serverino "$1" "$4"
+       ;;
+Darwin)
+       pass="$(echo "$3" | sed -e 's/\//%2F/g' -e 's/+/%2B/g')" &&
+       mount -t smbfs,soft "smb://$2:$pass@${1#//}" "$4"
+       ;;
+*)
+       die "No support for $(uname -s)"
+       ;;
+esac ||
+die "Could not mount $4"
index d55460a21287018aafe8a4133dc5238e50496a36..e688a26f0d61464c473fd7f0c7fce1a805269b2c 100755 (executable)
@@ -3,7 +3,7 @@
 # Print output of failing tests
 #
 
-. ${0%/*}/lib-travisci.sh
+. ${0%/*}/lib.sh
 
 # Tracing executed commands would produce too much noise in the loop below.
 set +x
@@ -38,6 +38,19 @@ do
                test_name="${TEST_EXIT%.exit}"
                test_name="${test_name##*/}"
                trash_dir="trash directory.$test_name"
+               case "$CI_TYPE" in
+               travis)
+                       ;;
+               azure-pipelines)
+                       mkdir -p failed-test-artifacts
+                       mv "$trash_dir" failed-test-artifacts
+                       continue
+                       ;;
+               *)
+                       echo "Unhandled CI type: $CI_TYPE" >&2
+                       exit 1
+                       ;;
+               esac
                trash_tgz_b64="trash.$test_name.base64"
                if [ -d "$trash_dir" ]
                then
index 84431c097eb8dffa1b540ee61d6c047239afd18a..cdd291344047394b26c17635d4507ad7ce14b542 100755 (executable)
@@ -3,11 +3,14 @@
 # Build and test Git
 #
 
-. ${0%/*}/lib-travisci.sh
+. ${0%/*}/lib.sh
 
-ln -s "$cache_dir/.prove" t/.prove
+case "$CI_OS_NAME" in
+windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
+*) ln -s "$cache_dir/.prove" t/.prove;;
+esac
 
-make --jobs=2
+make
 make test
 if test "$jobname" = "linux-gcc"
 then
index 26c168a0165522655e4e80988ba553b314b333f5..e3a193adbce39cb62b3be20b269444188a900613 100755 (executable)
@@ -55,6 +55,6 @@ linux32 --32bit i386 su -m -l $CI_USER -c '
        set -ex
        cd /usr/src/git
        test -n "$cache_dir" && ln -s "$cache_dir/.prove" t/.prove
-       make --jobs=2
+       make
        make test
 '
index 21637903ce0790b1689907f64cca302b32285028..751acfcf8a8c5c894f40e7fe2e96b8a395f2eab4 100755 (executable)
@@ -3,7 +3,7 @@
 # Download and run Docker image to build and test 32-bit Git
 #
 
-. ${0%/*}/lib-travisci.sh
+. ${0%/*}/lib.sh
 
 docker pull daald/ubuntu32:xenial
 
index 5688f261d0813d4b2b55baeb5e404fcfef80b5d7..a19aa7ebbc0931fa56acfccdcc0d8bc1ad94ea2c 100755 (executable)
@@ -3,9 +3,9 @@
 # Perform various static code analysis checks
 #
 
-. ${0%/*}/lib-travisci.sh
+. ${0%/*}/lib.sh
 
-make --jobs=2 coccicheck
+make coccicheck
 
 set +x
 
diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh
new file mode 100755 (executable)
index 0000000..f8c2c31
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Test Git in parallel
+#
+
+. ${0%/*}/lib.sh
+
+case "$CI_OS_NAME" in
+windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
+*) ln -s "$cache_dir/.prove" t/.prove;;
+esac
+
+make --quiet -C t T="$(cd t &&
+       ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh |
+       tr '\n' ' ')"
+
+check_unignored_build_artifacts
index d99a180e528084a43dff3f640ad3036f4f9dbf07..a73a4eca0aa6053913aa81846453a3257af31595 100755 (executable)
@@ -6,7 +6,7 @@
 # supported) and a commit hash.
 #
 
-. ${0%/*}/lib-travisci.sh
+. ${0%/*}/lib.sh
 
 test $# -ne 2 && echo "Unexpected number of parameters" && exit 1
 test -z "$GFW_CI_TOKEN" && echo "GFW_CI_TOKEN not defined" && exit
index a20de9ca127f82ba916534dfb12f3932595dda05..be3b7d376ac47391be71a3822a320e327f7a3a2d 100755 (executable)
@@ -3,15 +3,16 @@
 # Perform sanity checks on documentation and build it.
 #
 
-. ${0%/*}/lib-travisci.sh
+. ${0%/*}/lib.sh
 
+test -n "$ALREADY_HAVE_ASCIIDOCTOR" ||
 gem install asciidoctor
 
 make check-builtins
 make check-docs
 
 # Build docs with AsciiDoc
-make --jobs=2 doc > >(tee stdout.log) 2> >(tee stderr.log >&2)
+make doc > >(tee stdout.log) 2> >(tee stderr.log >&2)
 ! test -s stderr.log
 test -s Documentation/git.html
 test -s Documentation/git.xml
@@ -23,7 +24,7 @@ check_unignored_build_artifacts
 
 # Build docs with AsciiDoctor
 make clean
-make --jobs=2 USE_ASCIIDOCTOR=1 doc > >(tee stdout.log) 2> >(tee stderr.log >&2)
+make USE_ASCIIDOCTOR=1 doc > >(tee stdout.log) 2> >(tee stderr.log >&2)
 sed '/^GIT_VERSION = / d' stderr.log
 ! test -s stderr.log
 test -s Documentation/git.html
index 0af86840197deb3b0b9d442e3e5236170c31a4de..4276297595e21064abe2c0fb496f90d39455e200 100644 (file)
@@ -2175,7 +2175,7 @@ static void stop_timer_thread(void)
        if (timer_event)
                SetEvent(timer_event);  /* tell thread to terminate */
        if (timer_thread) {
-               int rc = WaitForSingleObject(timer_thread, 1000);
+               int rc = WaitForSingleObject(timer_thread, 10000);
                if (rc == WAIT_TIMEOUT)
                        error("timer thread did not terminate timely");
                else if (rc != WAIT_OBJECT_0)
index 348715f0e4bcfebf9df680fda5b9c4be4f3e7527..91cf5772fe5643dbe075da98ed5166e1899b9a54 100644 (file)
@@ -2,3 +2,4 @@
 /test-results
 /.prove
 /chainlinttmp
+/out/
index 6140b8c45bc7890c9166d44b852782364fa675ca..1326fd7505fde3a9e34fef1e197623c8d409d6ff 100644 (file)
--- a/t/README
+++ b/t/README
@@ -170,6 +170,15 @@ appropriately before running "make".
        implied by other options like --valgrind and
        GIT_TEST_INSTALLED.
 
+--no-bin-wrappers::
+       By default, the test suite uses the wrappers in
+       `../bin-wrappers/` to execute `git` and friends. With this option,
+       `../git` and friends are run directly. This is not recommended
+       in general, as the wrappers contain safeguards to ensure that no
+       files from an installed Git are used, but can speed up test runs
+       especially on platforms where running shell scripts is expensive
+       (most notably, Windows).
+
 --root=<directory>::
        Create "trash" directories used to store all temporary data during
        testing under <directory>, instead of the t/ directory.
index a47bfa3003ba72d9ccac56981bf2ed89302af802..b3253803ac8ce66625e346fcbce3ca9c9a16ae55 100644 (file)
@@ -8,6 +8,7 @@ static const char *usage_msg = "\n"
 "  test-tool date parse [date]...\n"
 "  test-tool date approxidate [date]...\n"
 "  test-tool date timestamp [date]...\n"
+"  test-tool date getnanos [start-nanos]\n"
 "  test-tool date is64bit\n"
 "  test-tool date time_t-is64bit\n";
 
@@ -91,6 +92,15 @@ static void parse_approx_timestamp(const char **argv, struct timeval *now)
        }
 }
 
+static void getnanos(const char **argv)
+{
+       double seconds = getnanotime() / 1.0e9;
+
+       if (*argv)
+               seconds -= strtod(*argv, NULL);
+       printf("%lf\n", seconds);
+}
+
 int cmd__date(int argc, const char **argv)
 {
        struct timeval now;
@@ -119,6 +129,8 @@ int cmd__date(int argc, const char **argv)
                parse_approxidate(argv+1, &now);
        else if (!strcmp(*argv, "timestamp"))
                parse_approx_timestamp(argv+1, &now);
+       else if (!strcmp(*argv, "getnanos"))
+               getnanos(argv+1);
        else if (!strcmp(*argv, "is64bit"))
                return sizeof(timestamp_t) == 8 ? 0 : 1;
        else if (!strcmp(*argv, "time_t-is64bit"))
index ae091d9b3e63cb506b2530217d40048b301faaa3..5d543ad21f89c76534e8efe32d6ead512dc7d0c3 100644 (file)
@@ -177,6 +177,14 @@ static int is_dotgitmodules(const char *path)
        return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path);
 }
 
+static int cmp_by_st_size(const void *a, const void *b)
+{
+       intptr_t x = (intptr_t)((struct string_list_item *)a)->util;
+       intptr_t y = (intptr_t)((struct string_list_item *)b)->util;
+
+       return x > y ? -1 : (x < y ? +1 : 0);
+}
+
 int cmd__path_utils(int argc, const char **argv)
 {
        if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
@@ -291,6 +299,62 @@ int cmd__path_utils(int argc, const char **argv)
                return !!res;
        }
 
+       if (argc > 2 && !strcmp(argv[1], "file-size")) {
+               int res = 0, i;
+               struct stat st;
+
+               for (i = 2; i < argc; i++)
+                       if (stat(argv[i], &st))
+                               res = error_errno("Cannot stat '%s'", argv[i]);
+                       else
+                               printf("%"PRIuMAX"\n", (uintmax_t)st.st_size);
+               return !!res;
+       }
+
+       if (argc == 4 && !strcmp(argv[1], "skip-n-bytes")) {
+               int fd = open(argv[2], O_RDONLY), offset = atoi(argv[3]);
+               char buffer[65536];
+
+               if (fd < 0)
+                       die_errno("could not open '%s'", argv[2]);
+               if (lseek(fd, offset, SEEK_SET) < 0)
+                       die_errno("could not skip %d bytes", offset);
+               for (;;) {
+                       ssize_t count = read(fd, buffer, sizeof(buffer));
+                       if (count < 0)
+                               die_errno("could not read '%s'", argv[2]);
+                       if (!count)
+                               break;
+                       if (write(1, buffer, count) < 0)
+                               die_errno("could not write to stdout");
+               }
+               close(fd);
+               return 0;
+       }
+
+       if (argc > 5 && !strcmp(argv[1], "slice-tests")) {
+               int res = 0;
+               long offset, stride, i;
+               struct string_list list = STRING_LIST_INIT_NODUP;
+               struct stat st;
+
+               offset = strtol(argv[2], NULL, 10);
+               stride = strtol(argv[3], NULL, 10);
+               if (stride < 1)
+                       stride = 1;
+               for (i = 4; i < argc; i++)
+                       if (stat(argv[i], &st))
+                               res = error_errno("Cannot stat '%s'", argv[i]);
+                       else
+                               string_list_append(&list, argv[i])->util =
+                                       (void *)(intptr_t)st.st_size;
+               QSORT(list.items, list.nr, cmp_by_st_size);
+               for (i = offset; i < list.nr; i+= stride)
+                       printf("%s\n", list.items[i].string);
+
+               return !!res;
+       }
+
        fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
                argv[1] ? argv[1] : "(there was none)");
        return 1;
index 5b137874e1d21166c92d00f540f6ecd68c18780a..50c55f8b1a3baf2df88214824b01fb707966ebc7 100644 (file)
@@ -51,6 +51,7 @@ static struct test_cmd cmds[] = {
        { "submodule-nested-repo-config", cmd__submodule_nested_repo_config },
        { "subprocess", cmd__subprocess },
        { "urlmatch-normalization", cmd__urlmatch_normalization },
+       { "xml-encode", cmd__xml_encode },
        { "wildmatch", cmd__wildmatch },
 #ifdef GIT_WINDOWS_NATIVE
        { "windows-named-pipe", cmd__windows_named_pipe },
index a396c10947e86bb88e47a8e4df4fef2de64024e8..a563df49bf086b92eb24b3f90f6194eed059de81 100644 (file)
@@ -48,6 +48,7 @@ int cmd__submodule_config(int argc, const char **argv);
 int cmd__submodule_nested_repo_config(int argc, const char **argv);
 int cmd__subprocess(int argc, const char **argv);
 int cmd__urlmatch_normalization(int argc, const char **argv);
+int cmd__xml_encode(int argc, const char **argv);
 int cmd__wildmatch(int argc, const char **argv);
 #ifdef GIT_WINDOWS_NATIVE
 int cmd__windows_named_pipe(int argc, const char **argv);
diff --git a/t/helper/test-xml-encode.c b/t/helper/test-xml-encode.c
new file mode 100644 (file)
index 0000000..a648bbd
--- /dev/null
@@ -0,0 +1,80 @@
+#include "test-tool.h"
+
+static const char *utf8_replace_character = "&#xfffd;";
+
+/*
+ * Encodes (possibly incorrect) UTF-8 on <stdin> to <stdout>, to be embedded
+ * in an XML file.
+ */
+int cmd__xml_encode(int argc, const char **argv)
+{
+       unsigned char buf[1024], tmp[4], *tmp2 = NULL;
+       ssize_t cur = 0, len = 1, remaining = 0;
+       unsigned char ch;
+
+       for (;;) {
+               if (++cur == len) {
+                       len = xread(0, buf, sizeof(buf));
+                       if (!len)
+                               return 0;
+                       if (len < 0)
+                               die_errno("Could not read <stdin>");
+                       cur = 0;
+               }
+               ch = buf[cur];
+
+               if (tmp2) {
+                       if ((ch & 0xc0) != 0x80) {
+                               fputs(utf8_replace_character, stdout);
+                               tmp2 = NULL;
+                               cur--;
+                               continue;
+                       }
+                       *tmp2 = ch;
+                       tmp2++;
+                       if (--remaining == 0) {
+                               fwrite(tmp, tmp2 - tmp, 1, stdout);
+                               tmp2 = NULL;
+                       }
+                       continue;
+               }
+
+               if (!(ch & 0x80)) {
+                       /* 0xxxxxxx */
+                       if (ch == '&')
+                               fputs("&amp;", stdout);
+                       else if (ch == '\'')
+                               fputs("&apos;", stdout);
+                       else if (ch == '"')
+                               fputs("&quot;", stdout);
+                       else if (ch == '<')
+                               fputs("&lt;", stdout);
+                       else if (ch == '>')
+                               fputs("&gt;", stdout);
+                       else if (ch >= 0x20)
+                               fputc(ch, stdout);
+                       else if (ch == 0x09 || ch == 0x0a || ch == 0x0d)
+                               fprintf(stdout, "&#x%02x;", ch);
+                       else
+                               fputs(utf8_replace_character, stdout);
+               } else if ((ch & 0xe0) == 0xc0) {
+                       /* 110XXXXx 10xxxxxx */
+                       tmp[0] = ch;
+                       remaining = 1;
+                       tmp2 = tmp + 1;
+               } else if ((ch & 0xf0) == 0xe0) {
+                       /* 1110XXXX 10Xxxxxx 10xxxxxx */
+                       tmp[0] = ch;
+                       remaining = 2;
+                       tmp2 = tmp + 1;
+               } else if ((ch & 0xf8) == 0xf0) {
+                       /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
+                       tmp[0] = ch;
+                       remaining = 3;
+                       tmp2 = tmp + 1;
+               } else
+                       fputs(utf8_replace_character, stdout);
+       }
+
+       return 0;
+}
index fd5f1ac649dc411bf3c70dcb54f7965e4006aca8..e10f5f787fca8b3f7789bcf9043d7c81929f070e 100755 (executable)
@@ -24,7 +24,7 @@ generate_random_characters () {
 }
 
 file_size () {
-       perl -e 'print -s $ARGV[0]' "$1"
+       test-tool path-utils file-size "$1"
 }
 
 filter_git () {
index 9c7604dcabec86776c3bb25598e39dac79118765..ebc49561acc08e916b2ab78cd84e6729c65f5803 100755 (executable)
@@ -166,7 +166,8 @@ test_trace () {
        expect="$1"
        shift
        GIT_TRACE=1 test-tool run-command "$@" run-command true 2>&1 >/dev/null | \
-               sed -e 's/.* run_command: //' -e '/trace: .*/d' >actual &&
+               sed -e 's/.* run_command: //' -e '/trace: .*/d' \
+                       -e '/RUNTIME_PREFIX requested/d' >actual &&
        echo "$expect true" >expect &&
        test_cmp expect actual
 }
index 1a9b21b2934b0a4fae7f74bb8c4da65d32218ca0..dcb4dbba673eb2eb6e13117babb6b75e8e59a49a 100755 (executable)
@@ -8,7 +8,7 @@ test_description='adding and checking out large blobs'
 # This should be moved to test-lib.sh together with the
 # copy in t0021 after both topics have graduated to 'master'.
 file_size () {
-       perl -e 'print -s $ARGV[0]' "$1"
+       test-tool path-utils file-size "$1"
 }
 
 test_expect_success setup '
index 34c47dae09966bb3b1de8178769b078509625b1e..df970d75845e782980835d853176acb580ab70d4 100755 (executable)
@@ -7,7 +7,7 @@ test_description='pack-object compression configuration'
 # This should be moved to test-lib.sh together with the
 # copy in t0021 after both topics have graduated to 'master'.
 file_size () {
-       perl -e 'print -s $ARGV[0]' "$1"
+       test-tool path-utils file-size "$1"
 }
 
 test_expect_success setup '
index 856219f46a7fc47332789d66a13f6fd4603520cb..5045f02a5331fcf4cb3973e81cff22620f0ad628 100755 (executable)
@@ -6,7 +6,7 @@ test_description='compression setting of fast-import utility'
 # This should be moved to test-lib.sh together with the
 # copy in t0021 after both topics have graduated to 'master'.
 file_size () {
-       perl -e 'print -s $ARGV[0]' "$1"
+       test-tool path-utils file-size "$1"
 }
 
 import_large () {
index 9876b4bab0044c73bca5eaccdfa5f6611f3f59b7..42b1a0aa7f0b8f5b06735293089f640b7f228f62 100644 (file)
@@ -111,6 +111,8 @@ do
                test -z "$HARNESS_ACTIVE" && quiet=t ;;
        --with-dashes)
                with_dashes=t ;;
+       --no-bin-wrappers)
+               no_bin_wrappers=t ;;
        --no-color)
                color= ;;
        --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
@@ -139,6 +141,9 @@ do
                verbose_log=t
                tee=t
                ;;
+       --write-junit-xml)
+               write_junit_xml=t
+               ;;
        --stress)
                stress=t ;;
        --stress=*)
@@ -622,11 +627,35 @@ trap 'exit $?' INT TERM HUP
 # the test_expect_* functions instead.
 
 test_ok_ () {
+       if test -n "$write_junit_xml"
+       then
+               write_junit_xml_testcase "$*"
+       fi
        test_success=$(($test_success + 1))
        say_color "" "ok $test_count - $@"
 }
 
 test_failure_ () {
+       if test -n "$write_junit_xml"
+       then
+               junit_insert="<failure message=\"not ok $test_count -"
+               junit_insert="$junit_insert $(xml_attr_encode "$1")\">"
+               junit_insert="$junit_insert $(xml_attr_encode \
+                       "$(if test -n "$GIT_TEST_TEE_OUTPUT_FILE"
+                          then
+                               test-tool path-utils skip-n-bytes \
+                                       "$GIT_TEST_TEE_OUTPUT_FILE" $GIT_TEST_TEE_OFFSET
+                          else
+                               printf '%s\n' "$@" | sed 1d
+                          fi)")"
+               junit_insert="$junit_insert</failure>"
+               if test -n "$GIT_TEST_TEE_OUTPUT_FILE"
+               then
+                       junit_insert="$junit_insert<system-err>$(xml_attr_encode \
+                               "$(cat "$GIT_TEST_TEE_OUTPUT_FILE")")</system-err>"
+               fi
+               write_junit_xml_testcase "$1" "      $junit_insert"
+       fi
        test_failure=$(($test_failure + 1))
        say_color error "not ok $test_count - $1"
        shift
@@ -635,11 +664,19 @@ test_failure_ () {
 }
 
 test_known_broken_ok_ () {
+       if test -n "$write_junit_xml"
+       then
+               write_junit_xml_testcase "$* (breakage fixed)"
+       fi
        test_fixed=$(($test_fixed+1))
        say_color error "ok $test_count - $@ # TODO known breakage vanished"
 }
 
 test_known_broken_failure_ () {
+       if test -n "$write_junit_xml"
+       then
+               write_junit_xml_testcase "$* (known breakage)"
+       fi
        test_broken=$(($test_broken+1))
        say_color warn "not ok $test_count - $@ # TODO known breakage"
 }
@@ -897,12 +934,21 @@ test_start_ () {
        test_count=$(($test_count+1))
        maybe_setup_verbose
        maybe_setup_valgrind
+       if test -n "$write_junit_xml"
+       then
+               junit_start=$(test-tool date getnanos)
+       fi
 }
 
 test_finish_ () {
        echo >&3 ""
        maybe_teardown_valgrind
        maybe_teardown_verbose
+       if test -n "$GIT_TEST_TEE_OFFSET"
+       then
+               GIT_TEST_TEE_OFFSET=$(test-tool path-utils file-size \
+                       "$GIT_TEST_TEE_OUTPUT_FILE")
+       fi
 }
 
 test_skip () {
@@ -934,6 +980,13 @@ test_skip () {
 
        case "$to_skip" in
        t)
+               if test -n "$write_junit_xml"
+               then
+                       message="$(xml_attr_encode "$skipped_reason")"
+                       write_junit_xml_testcase "$1" \
+                               "      <skipped message=\"$message\" />"
+               fi
+
                say_color skip >&3 "skipping test: $@"
                say_color skip "ok $test_count # skip $1 ($skipped_reason)"
                : true
@@ -949,9 +1002,51 @@ test_at_end_hook_ () {
        :
 }
 
+write_junit_xml () {
+       case "$1" in
+       --truncate)
+               >"$junit_xml_path"
+               junit_have_testcase=
+               shift
+               ;;
+       esac
+       printf '%s\n' "$@" >>"$junit_xml_path"
+}
+
+xml_attr_encode () {
+       printf '%s\n' "$@" | test-tool xml-encode
+}
+
+write_junit_xml_testcase () {
+       junit_attrs="name=\"$(xml_attr_encode "$this_test.$test_count $1")\""
+       shift
+       junit_attrs="$junit_attrs classname=\"$this_test\""
+       junit_attrs="$junit_attrs time=\"$(test-tool \
+               date getnanos $junit_start)\""
+       write_junit_xml "$(printf '%s\n' \
+               "    <testcase $junit_attrs>" "$@" "    </testcase>")"
+       junit_have_testcase=t
+}
+
 test_done () {
        GIT_EXIT_OK=t
 
+       if test -n "$write_junit_xml" && test -n "$junit_xml_path"
+       then
+               test -n "$junit_have_testcase" || {
+                       junit_start=$(test-tool date getnanos)
+                       write_junit_xml_testcase "all tests skipped"
+               }
+
+               # adjust the overall time
+               junit_time=$(test-tool date getnanos $junit_suite_start)
+               sed "s/<testsuite [^>]*/& time=\"$junit_time\"/" \
+                       <"$junit_xml_path" >"$junit_xml_path.new"
+               mv "$junit_xml_path.new" "$junit_xml_path"
+
+               write_junit_xml "  </testsuite>" "</testsuites>"
+       fi
+
        if test -z "$HARNESS_ACTIVE"
        then
                mkdir -p "$TEST_RESULTS_DIR"
@@ -1011,7 +1106,11 @@ test_done () {
                        error "Tests passed but trash directory already removed before test cleanup; aborting"
 
                        cd "$TRASH_DIRECTORY/.." &&
-                       rm -fr "$TRASH_DIRECTORY" ||
+                       rm -fr "$TRASH_DIRECTORY" || {
+                               # try again in a bit
+                               sleep 5;
+                               rm -fr "$TRASH_DIRECTORY"
+                       } ||
                        error "Tests passed but test cleanup failed; aborting"
                fi
                test_at_end_hook_
@@ -1117,20 +1216,25 @@ then
        PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$PATH
        GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
 else # normal case, use ../bin-wrappers only unless $with_dashes:
-       git_bin_dir="$GIT_BUILD_DIR/bin-wrappers"
-       if ! test -x "$git_bin_dir/git"
+       if test -n "$no_bin_wrappers"
        then
-               if test -z "$with_dashes"
+               with_dashes=t
+       else
+               git_bin_dir="$GIT_BUILD_DIR/bin-wrappers"
+               if ! test -x "$git_bin_dir/git"
                then
-                       say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH"
+                       if test -z "$with_dashes"
+                       then
+                               say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH"
+                       fi
+                       with_dashes=t
                fi
-               with_dashes=t
+               PATH="$git_bin_dir:$PATH"
        fi
-       PATH="$git_bin_dir:$PATH"
        GIT_EXEC_PATH=$GIT_BUILD_DIR
        if test -n "$with_dashes"
        then
-               PATH="$GIT_BUILD_DIR:$PATH"
+               PATH="$GIT_BUILD_DIR:$GIT_BUILD_DIR/t/helper:$PATH"
        fi
 fi
 GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt
@@ -1178,6 +1282,7 @@ then
 else
        mkdir -p "$TRASH_DIRECTORY"
 fi
+
 # Use -P to resolve symlinks in our working directory so that the cwd
 # in subprocesses like git equals our $PWD (for pathname comparisons).
 cd -P "$TRASH_DIRECTORY" || exit 1
@@ -1191,6 +1296,23 @@ then
        test_done
 fi
 
+if test -n "$write_junit_xml"
+then
+       junit_xml_dir="$TEST_OUTPUT_DIRECTORY/out"
+       mkdir -p "$junit_xml_dir"
+       junit_xml_base=${0##*/}
+       junit_xml_path="$junit_xml_dir/TEST-${junit_xml_base%.sh}.xml"
+       junit_attrs="name=\"${junit_xml_base%.sh}\""
+       junit_attrs="$junit_attrs timestamp=\"$(TZ=UTC \
+               date +%Y-%m-%dT%H:%M:%S)\""
+       write_junit_xml --truncate "<testsuites>" "  <testsuite $junit_attrs>"
+       junit_suite_start=$(test-tool date getnanos)
+       if test -n "$GIT_TEST_TEE_OUTPUT_FILE"
+       then
+               GIT_TEST_TEE_OFFSET=0
+       fi
+fi
+
 # Provide an implementation of the 'yes' utility
 yes () {
        if test $# = 0