diff options
54 files changed, 1294 insertions, 440 deletions
diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 83b6602aa..000000000 --- a/.travis.yml +++ /dev/null @@ -1,89 +0,0 @@ -# Travis-CI Build for libgit2 -# see travis-ci.org for details - -language: c - -os: - - linux - - osx - -compiler: - - gcc - - clang - -# Settings to try -env: - global: - - secure: "YnhS+8n6B+uoyaYfaJ3Lei7cSJqHDPiKJCKFIF2c87YDfmCvAJke8QtE7IzjYDs7UFkTCM4ox+ph2bERUrxZbSCyEkHdjIZpKuMJfYWja/jgMqTMxdyOH9y8JLFbZsSXDIXDwqBlC6vVyl1fP90M35wuWcNTs6tctfVWVofEFbs=" - - GITTEST_INVASIVE_FS_SIZE=1 - matrix: - - OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release" - - OPTIONS="-DTHREADSAFE=OFF -DBUILD_EXAMPLES=ON" - -addons: - apt: - sources: - - sourceline: 'deb https://dl.bintray.com/libgit2/ci-dependencies trusty libgit2deps' - key_url: 'https://bintray.com/user/downloadSubjectPublicKey?username=bintray' - packages: - - cmake - - curl - - libcurl3 - - libcurl3-gnutls - - libcurl4-gnutls-dev - - libssh2-1-dev - - openssh-client - - openssh-server - - valgrind - -sudo: false -osx_image: xcode8.3 - -matrix: - fast_finish: true - exclude: - - os: osx - compiler: gcc - include: - - compiler: gcc - env: COVERITY=1 - os: linux - - compiler: gcc - env: - - VALGRIND=1 - OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=OFF -DDEBUG_POOL=ON -DCMAKE_BUILD_TYPE=Debug" - os: linux - allow_failures: - - env: COVERITY=1 - -install: - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./script/install-deps-${TRAVIS_OS_NAME}.sh; fi - -# Run the Build script and tests -script: - - script/cibuild.sh - -# Run Tests -after_success: - - if [ "$TRAVIS_OS_NAME" = "linux" -a -n "$VALGRIND" ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline; fi - -# Only watch the development and master branches -branches: - only: - - master - - /^maint.*/ - -# Notify development list when needed -notifications: - irc: - channels: - - irc.freenode.net#libgit2 - on_success: change - on_failure: always - use_notice: true - skip_join: true - campfire: - on_success: always - on_failure: always - rooms: - - secure: "sH0dpPWMirbEe7AvLddZ2yOp8rzHalGmv0bYL/LIhVw3JDI589HCYckeLMSB\n3e/FeXw4bn0EqXWEXijVa4ijbilVY6d8oprdqMdWHEodng4KvY5vID3iZSGT\nxylhahO1XHmRynKQLOAvxlc93IlpVW38vQfby8giIY1nkpspb2w=" diff --git a/CHANGELOG.md b/CHANGELOG.md index a4903d392..68489feca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,51 @@ +v0.26.8 +------- + +This as a security release fixing the following list of issues: + +- The function family `git__strtol` is used to parse integers + from a buffer. As the functions do not take a buffer length as + argument, they will scan either until the end of the current + number or until a NUL byte is encountered. Many callers have + been misusing the function and called it on potentially + non-NUL-terminated buffers, resulting in possible out-of-bounds + reads. Callers have been fixed to use `git__strntol` functions + instead and `git__strtol` functions were removed. + +- The function `git__strntol64` relied on the undefined behavior + of signed integer overflows. While the code tried to detect + such overflows after they have happened, this is unspecified + behavior and may lead to weird behavior on uncommon platforms. + +- In the case where `git__strntol32` was unable to parse an + integer because it doesn't fit into an `int32_t`, it printed an + error message containing the string that is currently being + parsed. The code didn't truncate the string though, which + caused it to print the complete string until a NUL byte is + encountered and not only the currently parsed number. In case + where the string was not NUL terminated, this could have lead + to an out-of-bounds read. + +- When parsing tags, all unknown fields that appear before the + tag message are skipped. This skipping is done by using a plain + `strstr(buffer, "\n\n")` to search for the two newlines that + separate tag fields from tag message. As it is not possible to + supply a buffer length to `strstr`, this call may skip over the + buffer's end and thus result in an out of bounds read. As + `strstr` may return a pointer that is out of bounds, the + following computation of `buffer_end - buffer` will overflow + and result in an allocation of an invalid length. Note that + when reading objects from the object database, we make sure to + always NUL terminate them, making the use of `strstr` safe. + +- When parsing the "encoding" field of a commit, we may perform + an out of bounds read due to using `git__prefixcmp` instead of + `git__prefixncmp`. This can result in the parsed commit object + containing uninitialized data in both its message encoding and + message fields. Note that when reading objects from the object + database, we make sure to always NUL terminate them, making the + use of `strstr` safe. + v0.26.7 ------- diff --git a/CMakeLists.txt b/CMakeLists.txt index acce6fd89..2eca57d42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -677,7 +677,7 @@ IF (BUILD_CLAR) ADD_CUSTOM_COMMAND( OUTPUT ${CLAR_PATH}/clar.suite - COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline -xstress . + COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline -xstress -xperf . DEPENDS ${SRC_TEST} WORKING_DIRECTORY ${CLAR_PATH} ) @@ -703,17 +703,12 @@ IF (BUILD_CLAR) ENDIF () ENABLE_TESTING() - IF (WINHTTP OR OPENSSL_FOUND OR SECURITY_FOUND) - ADD_TEST(libgit2_clar libgit2_clar -ionline -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style) - ELSE () - ADD_TEST(libgit2_clar libgit2_clar -v -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style) - ENDIF () - # Add a test target which runs the cred callback tests, to be - # called after setting the url and user - ADD_TEST(libgit2_clar-cred_callback libgit2_clar -v -sonline::clone::cred_callback) - ADD_TEST(libgit2_clar-proxy_credentials_in_url libgit2_clar -v -sonline::clone::proxy_credentials_in_url) - ADD_TEST(libgit2_clar-proxy_credentials_request libgit2_clar -v -sonline::clone::proxy_credentials_request) + ADD_TEST(offline libgit2_clar -v -xonline) + ADD_TEST(online libgit2_clar -v -sonline) + ADD_TEST(gitdaemon libgit2_clar -v -sonline::push) + ADD_TEST(ssh libgit2_clar -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths) + ADD_TEST(proxy libgit2_clar -v -sonline::clone::proxy_credentials_in_url -sonline::clone::proxy_credentials_request) ENDIF () IF (TAGS) @@ -1,8 +1,7 @@ libgit2 - the Git linkable library ================================== -[![Travis Build Status](https://secure.travis-ci.org/libgit2/libgit2.svg?branch=master)](http://travis-ci.org/libgit2/libgit2) -[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/xvof5b4t5480a2q3/branch/master?svg=true)](https://ci.appveyor.com/project/libgit2/libgit2/branch/master) +[![Azure Pipelines Build Status](https://dev.azure.com/libgit2/libgit2/_apis/build/status/libgit2)](https://dev.azure.com/libgit2/libgit2/_build/latest?definitionId=7) [![Coverity Scan Build Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639) `libgit2` is a portable, pure C implementation of the Git core methods @@ -45,7 +44,7 @@ What It Can Do libgit2 provides you with the ability to manage Git repositories in the programming language of your choice. It's used in production to power many -applications including GitHub.com, Plastic SCM and Visual Studio Team Services. +applications including GitHub.com, Plastic SCM and Azure DevOps. It does not aim to replace the git tool or its user-facing commands. Some APIs resemble the plumbing commands as those align closely with the concepts of the diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index fb3fff7dd..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,50 +0,0 @@ -version: '{build}' -branches: - only: - - master - - appveyor - - /^maint.*/ -environment: - GITTEST_INVASIVE_FS_STRUCTURE: 1 - GITTEST_INVASIVE_FS_SIZE: 1 - - matrix: - - GENERATOR: "Visual Studio 11" - ARCH: 32 - - GENERATOR: "Visual Studio 11 Win64" - ARCH: 64 - - GENERATOR: "MSYS Makefiles" - ARCH: i686 # this is for 32-bit MinGW-w64 - - GENERATOR: "MSYS Makefiles" - ARCH: 64 -cache: -- i686-4.9.2-release-win32-sjlj-rt_v3-rev1.7z -- x86_64-4.9.2-release-win32-seh-rt_v3-rev1.7z - -build_script: -- ps: | - mkdir build - cd build - if ($env:GENERATOR -ne "MSYS Makefiles") { - cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON -D MSVC_CRTDBG=ON .. -G"$env:GENERATOR" - cmake --build . --config Debug - } -- cmd: | - if "%GENERATOR%"=="MSYS Makefiles" (C:\MinGW\msys\1.0\bin\sh --login /c/projects/libgit2/script/appveyor-mingw.sh) -test_script: -- ps: | - $ErrorActionPreference="Stop" - Start-FileDownload https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar -FileName poxyproxy.jar - # Run this early so we know it's ready by the time we need it - $proxyJob = Start-Job { java -jar $Env:APPVEYOR_BUILD_FOLDER\build\poxyproxy.jar -d --port 8080 --credentials foo:bar } - ctest -V -R libgit2_clar - $env:GITTEST_REMOTE_URL="https://github.com/libgit2/non-existent" - $env:GITTEST_REMOTE_USER="libgit2test" - ctest -V -R libgit2_clar-cred_callback - Receive-Job -Job $proxyJob - $env:GITTEST_REMOTE_PROXY_URL = "http://foo:bar@localhost:8080" - ctest -V -R libgit2_clar-proxy_credentials_in_url - $env:GITTEST_REMOTE_PROXY_URL = "http://localhost:8080" - $env:GITTEST_REMOTE_PROXY_USER = "foo" - $env:GITTEST_REMOTE_PROXY_PASS = "bar" - ctest -V -R libgit2_clar-proxy_credentials_request diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..64007d1a2 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,94 @@ +resources: +- repo: self + +trigger: +- master +- maint/* + +jobs: +- job: linux_trusty_gcc_openssl + displayName: 'Linux (Trusty; GCC; OpenSSL)' + pool: + vmImage: 'Ubuntu 16.04' + steps: + - template: ci/docker.yml + parameters: + imageName: 'libgit2/trusty-openssl:latest' + environmentVariables: | + CC=gcc + LEAK_CHECK=valgrind + +- job: linux_trusty_clang_openssl + displayName: 'Linux (Trusty; Clang; OpenSSL)' + pool: + vmImage: 'Ubuntu 16.04' + steps: + - template: ci/docker.yml + parameters: + imageName: 'libgit2/trusty-openssl:latest' + environmentVariables: | + CC=clang + LEAK_CHECK=valgrind + +- job: macos + displayName: 'macOS' + pool: + vmImage: 'macOS 10.13' + steps: + - bash: . '$(Build.SourcesDirectory)/ci/setup-osx.sh' + displayName: Setup + - template: ci/bash.yml + parameters: + environmentVariables: + TMPDIR: $(Agent.TempDirectory) + PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig + LEAK_CHECK: leaks + +- job: windows_vs_amd64 + displayName: 'Windows (Visual Studio; amd64)' + pool: Hosted + steps: + - template: ci/powershell.yml + parameters: + environmentVariables: + CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013 Win64" + +- job: windows_vs_x86 + displayName: 'Windows (Visual Studio; x86)' + pool: Hosted + steps: + - template: ci/powershell.yml + parameters: + environmentVariables: + CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013" + +- job: windows_mingw_amd64 + displayName: 'Windows (MinGW; amd64)' + pool: Hosted + steps: + - powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1' + displayName: Setup + env: + TEMP: $(Agent.TempDirectory) + ARCH: amd64 + - template: ci/powershell.yml + parameters: + environmentVariables: + CMAKE_OPTIONS: -G"MinGW Makefiles" + PATH: $(Agent.TempDirectory)\mingw64\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin + +- job: windows_mingw_x86 + displayName: 'Windows (MinGW; x86)' + pool: Hosted + steps: + - powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1' + displayName: Setup + workingDirectory: '$(Build.BinariesDirectory)' + env: + TEMP: $(Agent.TempDirectory) + ARCH: x86 + - template: ci/powershell.yml + parameters: + environmentVariables: + CMAKE_OPTIONS: -G"MinGW Makefiles" + PATH: $(Agent.TempDirectory)\mingw32\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin diff --git a/ci/bash.yml b/ci/bash.yml new file mode 100644 index 000000000..d776a3649 --- /dev/null +++ b/ci/bash.yml @@ -0,0 +1,17 @@ +# These are the steps used for building on machines with bash. +steps: +- bash: . '$(Build.SourcesDirectory)/ci/build.sh' + displayName: Build + workingDirectory: '$(Build.BinariesDirectory)' + env: ${{ parameters.environmentVariables }} +- bash: . '$(Build.SourcesDirectory)/ci/test.sh' + displayName: Test + workingDirectory: '$(Build.BinariesDirectory)' + env: ${{ parameters.environmentVariables }} +- task: PublishTestResults@2 + displayName: Publish Test Results + condition: succeededOrFailed() + inputs: + testResultsFiles: 'results_*.xml' + searchFolder: '$(Build.BinariesDirectory)' + mergeTestResults: true diff --git a/ci/build.ps1 b/ci/build.ps1 new file mode 100644 index 000000000..159c1dd1b --- /dev/null +++ b/ci/build.ps1 @@ -0,0 +1,30 @@ +Set-StrictMode -Version Latest + +$ErrorActionPreference = "Stop" +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +if ($Env:SOURCE_DIR) { $SourceDirectory = $Env:SOURCE_DIR } else { $SourceDirectory = Split-Path (Split-Path $MyInvocation.MyCommand.Path -Parent) -Parent } +$BuildDirectory = $(Get-Location).Path + +Write-Host "Source directory: ${SourceDirectory}" +Write-Host "Build directory: ${BuildDirectory}" +Write-Host "" +Write-Host "Operating system version:" +Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, ServicePackMajorVersion, BuildNumber, OSArchitecture | Format-List +Write-Host "PATH: ${Env:PATH}" +Write-Host "" + +Write-Host "##############################################################################" +Write-Host "## Configuring build environment" +Write-Host "##############################################################################" + +Invoke-Expression "cmake ${SourceDirectory} -DBUILD_EXAMPLES=ON ${Env:CMAKE_OPTIONS}" +if ($LastExitCode -ne 0) { [Environment]::Exit($LastExitCode) } + +Write-Host "" +Write-Host "##############################################################################" +Write-Host "## Building libgit2" +Write-Host "##############################################################################" + +cmake --build . +if ($LastExitCode -ne 0) { [Environment]::Exit($LastExitCode) } diff --git a/ci/build.sh b/ci/build.sh new file mode 100755 index 000000000..a1deab3f2 --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# +# Environment variables: +# +# SOURCE_DIR: Set to the directory of the libgit2 source (optional) +# If not set, it will be derived relative to this script. + +set -e + +SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} +BUILD_DIR=$(pwd) +CC=${CC:-cc} + +indent() { sed "s/^/ /"; } + +echo "Source directory: ${SOURCE_DIR}" +echo "Build directory: ${BUILD_DIR}" +echo "" +echo "Operating system version:" +uname -a 2>&1 | indent +echo "CMake version:" +cmake --version 2>&1 | indent +echo "Compiler version:" +$CC --version 2>&1 | indent +echo "" + +echo "##############################################################################" +echo "## Configuring build environment" +echo "##############################################################################" + +echo cmake ${SOURCE_DIR} -DBUILD_EXAMPLES=ON ${CMAKE_OPTIONS} +cmake ${SOURCE_DIR} -DBUILD_EXAMPLES=ON ${CMAKE_OPTIONS} + +echo "" +echo "##############################################################################" +echo "## Building libgit2" +echo "##############################################################################" + +cmake --build . diff --git a/script/coverity.sh b/ci/coverity.sh index 5fe16c031..a97fae8c8 100755 --- a/script/coverity.sh +++ b/ci/coverity.sh @@ -1,17 +1,13 @@ #!/bin/bash -set -e -# Only run this on our branches -echo "Branch: $TRAVIS_BRANCH | Pull request: $TRAVIS_PULL_REQUEST | Slug: $TRAVIS_REPO_SLUG" -if [ "$TRAVIS_BRANCH" != "master" -o "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_REPO_SLUG" != "libgit2/libgit2" ]; -then - echo "Only analyzing the 'master' brach of the main repository." - exit 0 -fi +set -e # Environment check [ -z "$COVERITY_TOKEN" ] && echo "Need to set a coverity token" && exit 1 +SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} +BUILD_DIR=$(pwd) + case $(uname -m) in i?86) BITS=32 ;; amd64|x86_64) BITS=64 ;; @@ -32,31 +28,29 @@ if [ ! -d "$TOOL_BASE" ]; then ln -s "$TOOL_DIR" "$TOOL_BASE"/cov-analysis fi -cp script/user_nodefs.h "$TOOL_BASE"/cov-analysis/config/user_nodefs.h +cp "${SOURCE_DIR}/script/user_nodefs.h" "$TOOL_BASE"/cov-analysis/config/user_nodefs.h COV_BUILD="$TOOL_BASE/cov-analysis/bin/cov-build" # Configure and build -rm -rf _build -mkdir _build -cd _build -cmake .. -DTHREADSAFE=ON +cmake ${SOURCE_DIR} + COVERITY_UNSUPPORTED=1 \ $COV_BUILD --dir cov-int \ cmake --build . # Upload results tar czf libgit2.tgz cov-int -SHA=$(git rev-parse --short HEAD) +SHA=$(cd ${SOURCE_DIR} && git rev-parse --short HEAD) HTML="$(curl \ --silent \ --write-out "\n%{http_code}" \ --form token="$COVERITY_TOKEN" \ - --form email=bs@github.com \ + --form email=libgit2@gmail.com \ --form file=@libgit2.tgz \ --form version="$SHA" \ - --form description="Travis build" \ + --form description="libgit2 build" \ https://scan.coverity.com/builds?project=libgit2)" # Body is everything up to the last line BODY="$(echo "$HTML" | head -n-1)" @@ -65,7 +59,7 @@ STATUS_CODE="$(echo "$HTML" | tail -n1)" echo "${BODY}" -if [ "${STATUS_CODE}" != "201" ]; then +if [ "${STATUS_CODE}" != "200" -a "${STATUS_CODE}" != "201" ]; then echo "Received error code ${STATUS_CODE} from Coverity" exit 1 fi diff --git a/ci/docker.yml b/ci/docker.yml new file mode 100644 index 000000000..e92510478 --- /dev/null +++ b/ci/docker.yml @@ -0,0 +1,33 @@ +# These are the steps used in a container-based build in VSTS. +steps: +- task: docker@0 + displayName: Build + inputs: + action: 'Run an image' + imageName: ${{ parameters.imageName }} + volumes: | + $(Build.SourcesDirectory):/src + $(Build.BinariesDirectory):/build + envVars: ${{ parameters.environmentVariables }} + workDir: '/build' + containerCommand: '/src/ci/build.sh' + detached: false +- task: docker@0 + displayName: Test + inputs: + action: 'Run an image' + imageName: ${{ parameters.imageName }} + volumes: | + $(Build.SourcesDirectory):/src + $(Build.BinariesDirectory):/build + envVars: ${{ parameters.environmentVariables }} + workDir: '/build' + containerCommand: '/src/ci/test.sh' + detached: false +- task: publishtestresults@2 + displayName: Publish Test Results + condition: succeededOrFailed() + inputs: + testResultsFiles: 'results_*.xml' + searchFolder: '$(Build.BinariesDirectory)' + mergeTestResults: true diff --git a/ci/nightly.yml b/ci/nightly.yml new file mode 100644 index 000000000..4d6d8a3e8 --- /dev/null +++ b/ci/nightly.yml @@ -0,0 +1,22 @@ +resources: +- repo: self + +jobs: +- job: coverity + displayName: 'Coverity' + pool: + vmImage: 'Ubuntu 16.04' + steps: + - task: Docker@0 + displayName: Build + inputs: + action: 'Run an image' + imageName: 'libgit2/trusty-openssl:latest' + volumes: | + $(Build.SourcesDirectory):/src + $(Build.BinariesDirectory):/build + envVars: | + COVERITY_TOKEN=$(COVERITY_TOKEN) + workDir: '/build' + containerCommand: '/src/ci/coverity.sh' + detached: false diff --git a/ci/powershell.yml b/ci/powershell.yml new file mode 100644 index 000000000..a2eb175d5 --- /dev/null +++ b/ci/powershell.yml @@ -0,0 +1,17 @@ +# These are the steps used for building on machines with PowerShell. +steps: +- powershell: . '$(Build.SourcesDirectory)\ci\build.ps1' + displayName: Build + workingDirectory: '$(Build.BinariesDirectory)' + env: ${{ parameters.environmentVariables }} +- powershell: . '$(Build.SourcesDirectory)\ci\test.ps1' + displayName: Test + workingDirectory: '$(Build.BinariesDirectory)' + env: ${{ parameters.environmentVariables }} +- task: PublishTestResults@2 + displayName: Publish Test Results + condition: succeededOrFailed() + inputs: + testResultsFiles: 'results_*.xml' + searchFolder: '$(Build.BinariesDirectory)' + mergeTestResults: true diff --git a/ci/setup-linux.sh b/ci/setup-linux.sh new file mode 100755 index 000000000..a0db14ee0 --- /dev/null +++ b/ci/setup-linux.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e +set -x + +TMPDIR=${TMPDIR:-/tmp} + +if [ -z "$SKIP_APT" ]; then + apt-get update + apt-get -y install build-essential pkg-config clang cmake openssl libssl-dev libssh2-1-dev libcurl4-gnutls-dev openssh-server +fi + +mkdir -p /var/run/sshd diff --git a/ci/setup-mingw.ps1 b/ci/setup-mingw.ps1 new file mode 100644 index 000000000..76ecd3987 --- /dev/null +++ b/ci/setup-mingw.ps1 @@ -0,0 +1,25 @@ +Set-StrictMode -Version Latest + +$ErrorActionPreference = "Stop" +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem"); + +Write-Host "##############################################################################" +Write-Host "## Downloading mingw" +Write-Host "##############################################################################" + +if ($env:ARCH -eq "amd64") { + $mingw_uri = "https://bintray.com/libgit2/build-dependencies/download_file?file_path=mingw-w64-x86_64-8.1.0-release-win32-seh-rt_v6-rev0.zip" + $platform = "x86_64" +} else { + $mingw_uri = "https://bintray.com/libgit2/build-dependencies/download_file?file_path=mingw-w64-i686-8.1.0-release-win32-sjlj-rt_v6-rev0.zip" + $platform = "x86" +} + +$wc = New-Object net.webclient +$wc.Downloadfile($mingw_uri, "${Env:TEMP}/mingw-${Env:ARCH}.zip") + +[System.IO.Compression.ZipFile]::ExtractToDirectory("${Env:TEMP}/mingw-${Env:ARCH}.zip", $Env:TEMP) diff --git a/ci/setup-osx.sh b/ci/setup-osx.sh new file mode 100755 index 000000000..564910e41 --- /dev/null +++ b/ci/setup-osx.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -x + +brew update +brew install pkgconfig zlib curl openssl libssh2 + +ln -s /Applications/Xcode.app/Contents/Developer/usr/lib/libLeaksAtExit.dylib /usr/local/lib diff --git a/ci/test.ps1 b/ci/test.ps1 new file mode 100644 index 000000000..1cf02118f --- /dev/null +++ b/ci/test.ps1 @@ -0,0 +1,72 @@ +Set-StrictMode -Version Latest + +$ErrorActionPreference = "Stop" +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +$SourceDir = Split-Path (Split-Path (Get-Variable MyInvocation).Value.MyCommand.Path) +$BuildDir = Get-Location +$global:Success = $true + +if ($Env:SKIP_TESTS) { exit } + +# Ask ctest what it would run if we were to invoke it directly. This lets +# us manage the test configuration in a single place (tests/CMakeLists.txt) +# instead of running clar here as well. But it allows us to wrap our test +# harness with a leak checker like valgrind. Append the option to write +# JUnit-style XML files. +function run_test { + $TestName = $args[0] + + $TestCommand = (ctest -N -V -R "^$TestName$") -join "`n" -replace "(?ms).*\n^[0-9]*: Test command: ","" -replace "\n.*","" + $TestCommand += " -r${BuildDir}\results_${TestName}.xml" + + Write-Host $TestCommand + Invoke-Expression $TestCommand + + if ($LastExitCode -ne 0) { $global:Success = $false } +} + +Write-Host "##############################################################################" +Write-Host "## Configuring test environment" +Write-Host "##############################################################################" + +if (-not $Env:SKIP_PROXY_TESTS) { + Write-Host "" + Write-Host "Starting HTTP proxy..." + Invoke-WebRequest -Method GET -Uri https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar -OutFile poxyproxy.jar + javaw -jar poxyproxy.jar -d --port 8080 --credentials foo:bar +} + +Write-Host "" +Write-Host "##############################################################################" +Write-Host "## Running (offline) tests" +Write-Host "##############################################################################" + +run_test offline + +if (-not $Env:SKIP_ONLINE_TESTS) { + Write-Host "" + Write-Host "##############################################################################" + Write-Host "## Running (online) tests" + Write-Host "##############################################################################" + + run_test online +} + +if (-not $Env:SKIP_PROXY_TESTS) { + Write-Host "" + Write-Host "Running proxy tests" + Write-Host "" + + $Env:GITTEST_REMOTE_PROXY_URL="localhost:8080" + $Env:GITTEST_REMOTE_PROXY_USER="foo" + $Env:GITTEST_REMOTE_PROXY_PASS="bar" + + run_test proxy + + taskkill /F /IM javaw.exe +} + +if (-Not $global:Success) { exit 1 } diff --git a/ci/test.sh b/ci/test.sh new file mode 100755 index 000000000..fea9d82d7 --- /dev/null +++ b/ci/test.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash + +set -e + +if [ -n "$SKIP_TESTS" ]; then + exit 0 +fi + +SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} +BUILD_DIR=$(pwd) +TMPDIR=${TMPDIR:-/tmp} +USER=${USER:-$(whoami)} + +SUCCESS=1 + +VALGRIND="valgrind --leak-check=full --show-reachable=yes --error-exitcode=125 --num-callers=50 --suppressions=\"$SOURCE_DIR/libgit2_clar.supp\"" +LEAKS="MallocStackLogging=1 MallocScribble=1 leaks -quiet -atExit -- nohup" + +cleanup() { + echo "Cleaning up..." + + if [ ! -z "$GITDAEMON_DIR" -a -f "${GITDAEMON_DIR}/pid" ]; then + echo "Stopping git daemon..." + kill $(cat "${GITDAEMON_DIR}/pid") + fi + + if [ ! -z "$SSHD_DIR" -a -f "${SSHD_DIR}/pid" ]; then + echo "Stopping SSH..." + kill $(cat "${SSHD_DIR}/pid") + fi + + echo "Done." +} + +failure() { + echo "Test exited with code: $1" + SUCCESS=0 +} + +# Ask ctest what it would run if we were to invoke it directly. This lets +# us manage the test configuration in a single place (tests/CMakeLists.txt) +# instead of running clar here as well. But it allows us to wrap our test +# harness with a leak checker like valgrind. Append the option to write +# JUnit-style XML files. +run_test() { + TEST_CMD=$(ctest -N -V -R "^${1}$" | sed -n 's/^[0-9]*: Test command: //p') + TEST_CMD="${TEST_CMD} -r${BUILD_DIR}/results_${1}.xml" + + if [ "$LEAK_CHECK" = "valgrind" ]; then + RUNNER="$VALGRIND $TEST_CMD" + elif [ "$LEAK_CHECK" = "leaks" ]; then + RUNNER="$LEAKS $TEST_CMD" + else + RUNNER="$TEST_CMD" + fi + + eval $RUNNER || failure +} + +# Configure the test environment; run them early so that we're certain +# that they're started by the time we need them. + +echo "##############################################################################" +echo "## Configuring test environment" +echo "##############################################################################" + +if [ -z "$SKIP_GITDAEMON_TESTS" ]; then + echo "Starting git daemon..." + GITDAEMON_DIR=`mktemp -d ${TMPDIR}/gitdaemon.XXXXXXXX` + git init --bare "${GITDAEMON_DIR}/test.git" + git daemon --listen=localhost --export-all --enable=receive-pack --pid-file="${GITDAEMON_DIR}/pid" --base-path="${GITDAEMON_DIR}" "${GITDAEMON_DIR}" 2>/dev/null & +fi + +if [ -z "$SKIP_PROXY_TESTS" ]; then + echo "Starting HTTP proxy..." + curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar >poxyproxy.jar + java -jar poxyproxy.jar -d --port 8080 --credentials foo:bar >/dev/null 2>&1 & +fi + +if [ -z "$SKIP_SSH_TESTS" ]; then + echo "Starting ssh daemon..." + HOME=`mktemp -d ${TMPDIR}/home.XXXXXXXX` + SSHD_DIR=`mktemp -d ${TMPDIR}/sshd.XXXXXXXX` + git init --bare "${SSHD_DIR}/test.git" + cat >"${SSHD_DIR}/sshd_config" <<-EOF + Port 2222 + ListenAddress 0.0.0.0 + Protocol 2 + HostKey ${SSHD_DIR}/id_rsa + PidFile ${SSHD_DIR}/pid + AuthorizedKeysFile ${HOME}/.ssh/authorized_keys + LogLevel DEBUG + RSAAuthentication yes + PasswordAuthentication yes + PubkeyAuthentication yes + ChallengeResponseAuthentication no + StrictModes no + # Required here as sshd will simply close connection otherwise + UsePAM no + EOF + ssh-keygen -t rsa -f "${SSHD_DIR}/id_rsa" -N "" -q + /usr/sbin/sshd -f "${SSHD_DIR}/sshd_config" -E "${SSHD_DIR}/log" + + # Set up keys + mkdir "${HOME}/.ssh" + ssh-keygen -t rsa -f "${HOME}/.ssh/id_rsa" -N "" -q + cat "${HOME}/.ssh/id_rsa.pub" >>"${HOME}/.ssh/authorized_keys" + while read algorithm key comment; do + echo "[localhost]:2222 $algorithm $key" >>"${HOME}/.ssh/known_hosts" + done <"${SSHD_DIR}/id_rsa.pub" + + # Get the fingerprint for localhost and remove the colons so we can + # parse it as a hex number. Older versions have a different output + # format. + if [[ $(ssh -V 2>&1) == OpenSSH_6* ]]; then + SSH_FINGERPRINT=$(ssh-keygen -F '[localhost]:2222' -f "${HOME}/.ssh/known_hosts" -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':') + else + SSH_FINGERPRINT=$(ssh-keygen -E md5 -F '[localhost]:2222' -f "${HOME}/.ssh/known_hosts" -l | tail -n 1 | cut -d ' ' -f 3 | cut -d : -f2- | tr -d :) + fi +fi + +# Run the tests that do not require network connectivity. + +if [ -z "$SKIP_OFFLINE_TESTS" ]; then + echo "" + echo "##############################################################################" + echo "## Running (offline) tests" + echo "##############################################################################" + + run_test offline +fi + +if [ -z "$SKIP_ONLINE_TESTS" ]; then + # Run the various online tests. The "online" test suite only includes the + # default online tests that do not require additional configuration. The + # "proxy" and "ssh" test suites require further setup. + + echo "" + echo "##############################################################################" + echo "## Running (online) tests" + echo "##############################################################################" + + run_test online +fi + +if [ -z "$SKIP_GITDAEMON_TESTS" ]; then + echo "" + echo "Running gitdaemon tests" + echo "" + + export GITTEST_REMOTE_URL="git://localhost/test.git" + run_test gitdaemon + unset GITTEST_REMOTE_URL +fi + +if [ -z "$SKIP_PROXY_TESTS" ]; then + echo "" + echo "Running proxy tests" + echo "" + + export GITTEST_REMOTE_PROXY_URL="localhost:8080" + export GITTEST_REMOTE_PROXY_USER="foo" + export GITTEST_REMOTE_PROXY_PASS="bar" + run_test proxy + unset GITTEST_REMOTE_PROXY_URL + unset GITTEST_REMOTE_PROXY_USER + unset GITTEST_REMOTE_PROXY_PASS +fi + +if [ -z "$SKIP_SSH_TESTS" ]; then + echo "" + echo "Running ssh tests" + echo "" + + export GITTEST_REMOTE_URL="ssh://localhost:2222/$SSHD_DIR/test.git" + export GITTEST_REMOTE_USER=$USER + export GITTEST_REMOTE_SSH_KEY="${HOME}/.ssh/id_rsa" + export GITTEST_REMOTE_SSH_PUBKEY="${HOME}/.ssh/id_rsa.pub" + export GITTEST_REMOTE_SSH_PASSPHRASE="" + export GITTEST_REMOTE_SSH_FINGERPRINT="${SSH_FINGERPRINT}" + run_test ssh + unset GITTEST_REMOTE_URL + unset GITTEST_REMOTE_USER + unset GITTEST_REMOTE_SSH_KEY + unset GITTEST_REMOTE_SSH_PUBKEY + unset GITTEST_REMOTE_SSH_PASSPHRASE + unset GITTEST_REMOTE_SSH_FINGERPRINT +fi + +cleanup + +if [ "$SUCCESS" -ne "1" ]; then + echo "Some tests failed." + exit 1 +fi + +echo "Success." +exit 0 diff --git a/examples/network/common.c b/examples/network/common.c index 1a81a10f8..b0afb0238 100644 --- a/examples/network/common.c +++ b/examples/network/common.c @@ -16,6 +16,43 @@ # define UNUSED(x) x #endif +static int readline(char **out) +{ + int c, error = 0, length = 0, allocated = 0; + char *line = NULL; + + errno = 0; + + while ((c = getchar()) != EOF) { + if (length == allocated) { + allocated += 16; + + if ((line = realloc(line, allocated)) == NULL) { + error = -1; + goto error; + } + } + + if (c == '\n') + break; + + line[length++] = c; + } + + if (errno != 0) { + error = -1; + goto error; + } + + line[length] = '\0'; + *out = line; + line = NULL; + error = length; +error: + free(line); + return error; +} + int cred_acquire_cb(git_cred **out, const char * UNUSED(url), const char * UNUSED(username_from_url), @@ -26,14 +63,14 @@ int cred_acquire_cb(git_cred **out, int error; printf("Username: "); - if (getline(&username, NULL, stdin) < 0) { + if (readline(&username) < 0) { fprintf(stderr, "Unable to read username: %s", strerror(errno)); return -1; } /* Yup. Right there on your terminal. Careful where you copy/paste output. */ printf("Password: "); - if (getline(&password, NULL, stdin) < 0) { + if (readline(&password) < 0) { fprintf(stderr, "Unable to read password: %s", strerror(errno)); free(username); return -1; diff --git a/include/git2/version.h b/include/git2/version.h index 520726ec5..df2351b42 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,10 +7,10 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "0.26.7" +#define LIBGIT2_VERSION "0.26.8" #define LIBGIT2_VER_MAJOR 0 #define LIBGIT2_VER_MINOR 26 -#define LIBGIT2_VER_REVISION 7 +#define LIBGIT2_VER_REVISION 8 #define LIBGIT2_VER_PATCH 0 #define LIBGIT2_SOVERSION 26 diff --git a/libgit2_clar.supp b/libgit2_clar.supp index bd22ada46..0cc89b57f 100644 --- a/libgit2_clar.supp +++ b/libgit2_clar.supp @@ -47,3 +47,28 @@ ... fun:__check_pf } + +{ + ignore-curl-global-init + Memcheck:Leak + ... + fun:curl_global_init +} + +{ + ignore-libssh2-gcrypt-leak + Memcheck:Leak + ... + fun:gcry_control + obj:*libssh2.so* +} + +{ + ignore-noai6ai_cached-double-free + Memcheck:Free + fun:free + fun:__libc_freeres + ... + fun:exit + ... +} diff --git a/script/appveyor-mingw.sh b/script/appveyor-mingw.sh deleted file mode 100755 index d171a72d5..000000000 --- a/script/appveyor-mingw.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -set -e -cd `dirname "$0"`/.. -if [ "$ARCH" = "i686" ]; then - f=i686-4.9.2-release-win32-sjlj-rt_v3-rev1.7z - if ! [ -e $f ]; then - curl -LsSO http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/4.9.2/threads-win32/sjlj/$f - fi - 7z x $f > /dev/null - export PATH=`pwd`/mingw32/bin:$PATH -else - f=x86_64-4.9.2-release-win32-seh-rt_v3-rev1.7z - if ! [ -e $f ]; then - curl -LsSO http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/$f - fi - 7z x $f > /dev/null - export PATH=`pwd`/mingw64/bin:$PATH -fi -cd build -gcc --version -cmake --version -cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON .. -G"$GENERATOR" -cmake --build . --config RelWithDebInfo diff --git a/script/cibuild.sh b/script/cibuild.sh index 403df223e..c062c112e 100755 --- a/script/cibuild.sh +++ b/script/cibuild.sh @@ -2,87 +2,26 @@ set -x -if [ -n "$COVERITY" ]; -then - ./script/coverity.sh; - exit $?; +if [ -n "$COVERITY" ]; then + ./script/coverity.sh + exit $? fi if [ "$TRAVIS_OS_NAME" = "osx" ]; then export PKG_CONFIG_PATH=$(ls -d /usr/local/Cellar/{curl,zlib}/*/lib/pkgconfig | paste -s -d':' -) -fi -# Should we ask Travis to cache this file? -curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar >poxyproxy.jar || exit $? -# Run this early so we know it's ready by the time we need it -java -jar poxyproxy.jar -d --port 8080 --credentials foo:bar & + # Set up a ramdisk for us to put our test data on to speed up tests on macOS + export CLAR_TMP="$HOME"/_clar_tmp + mkdir -p $CLAR_TMP + + # 5*2M sectors aka ~5GB of space + device=$(hdiutil attach -nomount ram://$((5 * 2 * 1024 * 1024))) + newfs_hfs $device + mount -t hfs $device $CLAR_TMP +fi mkdir _build cd _build # shellcheck disable=SC2086 cmake .. -DBUILD_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $? -make -j2 install || exit $? - -# If this platform doesn't support test execution, bail out now -if [ -n "$SKIP_TESTS" ]; -then - exit $?; -fi - -# Create a test repo which we can use for the online::push tests -mkdir "$HOME"/_temp -git init --bare "$HOME"/_temp/test.git -git daemon --listen=localhost --export-all --enable=receive-pack --base-path="$HOME"/_temp "$HOME"/_temp 2>/dev/null & -export GITTEST_REMOTE_URL="git://localhost/test.git" - -# Run the test suite -ctest -V -R libgit2_clar || exit $? - -# Now that we've tested the raw git protocol, let's set up ssh to we -# can do the push tests over it - -killall git-daemon - -if [ "$TRAVIS_OS_NAME" = "osx" ]; then - echo 'PasswordAuthentication yes' | sudo tee -a /etc/sshd_config -fi - -ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q -cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys -ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts - -# Get the fingerprint for localhost and remove the colons so we can parse it as -# a hex number. The Mac version is newer so it has a different output format. -if [ "$TRAVIS_OS_NAME" = "osx" ]; then - export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -E md5 -F localhost -l | tail -n 1 | cut -d ' ' -f 3 | cut -d : -f2- | tr -d :) -else - export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -F localhost -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':') -fi - -export GITTEST_REMOTE_URL="ssh://localhost/$HOME/_temp/test.git" -export GITTEST_REMOTE_USER=$USER -export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa" -export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub" -export GITTEST_REMOTE_SSH_PASSPHRASE="" - - -if [ -e ./libgit2_clar ]; then - ./libgit2_clar -sonline::push -sonline::clone::ssh_cert && - ./libgit2_clar -sonline::clone::ssh_with_paths || exit $? - if [ "$TRAVIS_OS_NAME" = "linux" ]; then - ./libgit2_clar -sonline::clone::cred_callback || exit $? - fi - - # Use the proxy we started at the beginning - export GITTEST_REMOTE_PROXY_URL="http://foo:bar@localhost:8080/" - ./libgit2_clar -sonline::clone::proxy_credentials_in_url || exit $? - export GITTEST_REMOTE_PROXY_URL="http://localhost:8080/" - export GITTEST_REMOTE_PROXY_USER="foo" - export GITTEST_REMOTE_PROXY_PASS="bar" - ./libgit2_clar -sonline::clone::proxy_credentials_request || exit $? - -fi - -export GITTEST_REMOTE_URL="https://github.com/libgit2/non-existent" -export GITTEST_REMOTE_USER="libgit2test" -ctest -V -R libgit2_clar-cred_callback +cmake --build . --target install || exit $? diff --git a/script/install-deps-osx.sh b/script/install-deps-osx.sh deleted file mode 100755 index 94314dbaa..000000000 --- a/script/install-deps-osx.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -set -x - -brew update -brew install zlib -brew install curl -brew install openssl -brew install libssh2 diff --git a/src/commit.c b/src/commit.c index 4a340058a..0ec989421 100644 --- a/src/commit.c +++ b/src/commit.c @@ -442,7 +442,7 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) while (eoln < buffer_end && *eoln != '\n') ++eoln; - if (git__prefixcmp(buffer, "encoding ") == 0) { + if (git__prefixncmp(buffer, buffer_end - buffer, "encoding ") == 0) { buffer += strlen("encoding "); commit->message_encoding = git__strndup(buffer, eoln - buffer); diff --git a/src/commit_list.c b/src/commit_list.c index 3bba58c27..7df79bfd6 100644 --- a/src/commit_list.c +++ b/src/commit_list.c @@ -171,7 +171,9 @@ static int commit_quick_parse( buffer--; } - if ((buffer == committer_start) || (git__strtol64(&commit_time, (char *)(buffer + 1), NULL, 10) < 0)) + if ((buffer == committer_start) || + (git__strntol64(&commit_time, (char *)(buffer + 1), + buffer_end - buffer + 1, NULL, 10) < 0)) return commit_error(commit, "cannot parse commit time"); commit->time = commit_time; diff --git a/src/config.c b/src/config.c index 169a62880..b0cb2c117 100644 --- a/src/config.c +++ b/src/config.c @@ -1298,7 +1298,7 @@ int git_config_parse_int64(int64_t *out, const char *value) const char *num_end; int64_t num; - if (!value || git__strtol64(&num, value, &num_end, 0) < 0) + if (!value || git__strntol64(&num, value, strlen(value), &num_end, 0) < 0) goto fail_parse; switch (*num_end) { diff --git a/src/curl_stream.c b/src/curl_stream.c index f07370f21..4e34ae2b9 100644 --- a/src/curl_stream.c +++ b/src/curl_stream.c @@ -328,7 +328,7 @@ int git_curl_stream_new(git_stream **out, const char *host, const char *port) return -1; } - if ((error = git__strtol32(&iport, port, NULL, 10)) < 0) { + if ((error = git__strntol32(&iport, port, strlen(port), NULL, 10)) < 0) { git__free(st); return error; } diff --git a/src/index.c b/src/index.c index c5f6550d6..d7952ac6e 100644 --- a/src/index.c +++ b/src/index.c @@ -2202,7 +2202,7 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) for (i = 0; i < 3; i++) { int64_t tmp; - if (git__strtol64(&tmp, buffer, &endptr, 8) < 0 || + if (git__strntol64(&tmp, buffer, size, &endptr, 8) < 0 || !endptr || endptr == buffer || *endptr || tmp < 0 || tmp > UINT32_MAX) { index_entry_reuc_free(lost); diff --git a/src/rebase.c b/src/rebase.c index f528031b3..6d31bf3ba 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -151,7 +151,7 @@ GIT_INLINE(int) rebase_readint( if ((error = rebase_readfile(asc_out, state_path, filename)) < 0) return error; - if (git__strtol32(&num, asc_out->ptr, &eol, 10) < 0 || num < 0 || *eol) { + if (git__strntol32(&num, asc_out->ptr, asc_out->size, &eol, 10) < 0 || num < 0 || *eol) { giterr_set(GITERR_REBASE, "the file '%s' contains an invalid numeric value", filename); return -1; } diff --git a/src/revparse.c b/src/revparse.c index fd6bd1ea6..927e83073 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -127,7 +127,8 @@ static int try_parse_numeric(int *n, const char *curly_braces_content) int32_t content; const char *end_ptr; - if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0) + if (git__strntol32(&content, curly_braces_content, strlen(curly_braces_content), + &end_ptr, 10) < 0) return -1; if (*end_ptr != '\0') @@ -577,7 +578,7 @@ static int extract_how_many(int *n, const char *spec, size_t *pos) } while (spec[(*pos)] == kind && kind == '~'); if (git__isdigit(spec[*pos])) { - if (git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0) + if (git__strntol32(&parsed, spec + *pos, strlen(spec + *pos), &end_ptr, 10) < 0) return GIT_EINVALIDSPEC; accumulated += (parsed - 1); diff --git a/src/signature.c b/src/signature.c index 25e0ee723..2e0cfe01a 100644 --- a/src/signature.c +++ b/src/signature.c @@ -228,7 +228,8 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, const char *time_start = email_end + 2; const char *time_end; - if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0) { + if (git__strntol64(&sig->when.time, time_start, + buffer_end - time_start, &time_end, 10) < 0) { git__free(sig->name); git__free(sig->email); sig->name = sig->email = NULL; @@ -243,8 +244,9 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, tz_start = time_end + 1; if ((tz_start[0] != '-' && tz_start[0] != '+') || - git__strtol32(&offset, tz_start + 1, &tz_end, 10) < 0) { - //malformed timezone, just assume it's zero + git__strntol32(&offset, tz_start + 1, + buffer_end - tz_start + 1, &tz_end, 10) < 0) { + /* malformed timezone, just assume it's zero */ offset = 0; } @@ -70,10 +70,9 @@ static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end) static const char *tag_types[] = { NULL, "commit\n", "tree\n", "blob\n", "tag\n" }; - - unsigned int i; size_t text_len, alloc_len; - char *search; + const char *search; + unsigned int i; if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0) return tag_error("object field invalid"); @@ -138,8 +137,9 @@ static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end) tag->message = NULL; if (buffer < buffer_end) { /* If we're not at the end of the header, search for it */ - if( *buffer != '\n' ) { - search = strstr(buffer, "\n\n"); + if(*buffer != '\n') { + search = git__memmem(buffer, buffer_end - buffer, + "\n\n", 2); if (search) buffer = search + 1; else diff --git a/src/transports/smart.c b/src/transports/smart.c index a96fdf6fb..5cf107dd9 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -43,6 +43,11 @@ GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransp t->current_stream = NULL; } + if (t->url) { + git__free(t->url); + t->url = NULL; + } + if (close_subtransport && t->wrapped->close(t->wrapped) < 0) return -1; diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index e726d0777..0e05ff861 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -391,7 +391,7 @@ static int parse_len(size_t *out, const char *line, size_t linelen) } } - if ((error = git__strtol32(&len, num, &num_end, 16)) < 0) + if ((error = git__strntol32(&len, num, PKT_LEN_SIZE, &num_end, 16)) < 0) return error; if (len < 0) diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index cf6445f53..c54e16d0f 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -764,7 +764,8 @@ static int winhttp_connect( t->connection = NULL; /* Prepare port */ - if (git__strtol32(&port, t->connection_data.port, NULL, 10) < 0) + if (git__strntol32(&port, t->connection_data.port, + strlen(t->connection_data.port), NULL, 10) < 0) return -1; /* Prepare host */ diff --git a/src/tree-cache.c b/src/tree-cache.c index 548054136..17f235977 100644 --- a/src/tree-cache.c +++ b/src/tree-cache.c @@ -90,7 +90,7 @@ static int read_tree_internal(git_tree_cache **out, return -1; /* Blank-terminated ASCII decimal number of entries in this tree */ - if (git__strtol32(&count, buffer, &buffer, 10) < 0) + if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0) goto corrupted; tree->entry_count = count; @@ -99,7 +99,7 @@ static int read_tree_internal(git_tree_cache **out, goto corrupted; /* Number of children of the tree, newline-terminated */ - if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < 0) + if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0 || count < 0) goto corrupted; tree->children_count = count; diff --git a/src/util.c b/src/util.c index 964b0ab6a..1e7de93c3 100644 --- a/src/util.c +++ b/src/util.c @@ -64,12 +64,6 @@ int git_strarray_copy(git_strarray *tgt, const git_strarray *src) return 0; } -int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int base) -{ - - return git__strntol64(result, nptr, (size_t)-1, endptr, base); -} - int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) { const char *p; @@ -128,10 +122,20 @@ int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const cha v = c - 'A' + 10; if (v >= base) break; - nn = n * base + (neg ? -v : v); - if ((!neg && nn < n) || (neg && nn > n)) + v = neg ? -v : v; + if (n > INT64_MAX / base || n < INT64_MIN / base) { ovfl = 1; - n = nn; + /* Keep on iterating until the end of this number */ + continue; + } + nn = n * base; + if ((v > 0 && nn > INT64_MAX - v) || + (v < 0 && nn < INT64_MIN - v)) { + ovfl = 1; + /* Keep on iterating until the end of this number */ + continue; + } + n = nn + v; } Return: @@ -152,28 +156,26 @@ Return: return 0; } -int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base) -{ - - return git__strntol32(result, nptr, (size_t)-1, endptr, base); -} - int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) { - int error; + const char *tmp_endptr; int32_t tmp_int; int64_t tmp_long; + int error; - if ((error = git__strntol64(&tmp_long, nptr, nptr_len, endptr, base)) < 0) + if ((error = git__strntol64(&tmp_long, nptr, nptr_len, &tmp_endptr, base)) < 0) return error; tmp_int = tmp_long & 0xFFFFFFFF; if (tmp_int != tmp_long) { - giterr_set(GITERR_INVALID, "failed to convert: '%s' is too large", nptr); + int len = tmp_endptr - nptr; + giterr_set(GITERR_INVALID, "failed to convert: '%.*s' is too large", len, nptr); return -1; } *result = tmp_int; + if (endptr) + *endptr = tmp_endptr; return error; } @@ -351,6 +353,47 @@ size_t git__linenlen(const char *buffer, size_t buffer_len) return nl ? (size_t)(nl - buffer) + 1 : buffer_len; } +/* + * Adapted Not So Naive algorithm from http://www-igm.univ-mlv.fr/~lecroq/string/ + */ +const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen) +{ + const char *h, *n; + size_t j, k, l; + + if (needlelen > haystacklen || !haystacklen || !needlelen) + return NULL; + + h = (const char *) haystack, + n = (const char *) needle; + + if (needlelen == 1) + return memchr(haystack, *n, haystacklen); + + if (n[0] == n[1]) { + k = 2; + l = 1; + } else { + k = 1; + l = 2; + } + + j = 0; + while (j <= haystacklen - needlelen) { + if (n[1] != h[j + 1]) { + j += k; + } else { + if (memcmp(n + 2, h + j + 2, needlelen - 2) == 0 && + n[0] == h[j]) + return h + j; + j += l; + } + } + + return NULL; +} + void git__hexdump(const char *buffer, size_t len) { static const size_t LINE_WIDTH = 16; diff --git a/src/util.h b/src/util.h index 8e666f9de..2ac118d8f 100644 --- a/src/util.h +++ b/src/util.h @@ -263,9 +263,7 @@ GIT_INLINE(int) git__signum(int val) return ((val > 0) - (val < 0)); } -extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base); extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); -extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base); extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); @@ -318,6 +316,9 @@ GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n) return NULL; } +extern const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); + typedef int (*git__tsort_cmp)(const void *a, const void *b); extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); diff --git a/tests/buf/oom.c b/tests/buf/oom.c index b9fd29cbb..16a03cc1a 100644 --- a/tests/buf/oom.c +++ b/tests/buf/oom.c @@ -1,10 +1,22 @@ #include "clar_libgit2.h" #include "buffer.h" -#if defined(GIT_ARCH_64) -#define TOOBIG 0xffffffffffffff00 +/* + * We want to use some ridiculous size that `malloc` will fail with + * but that does not otherwise interfere with testing. On Linux, choose + * a number that is large enough to fail immediately but small enough + * that valgrind doesn't believe it to erroneously be a negative number. + * On macOS, choose a number that is large enough to fail immediately + * without having libc print warnings to stderr. + */ +#if defined(GIT_ARCH_64) && defined(__linux__) +# define TOOBIG 0x0fffffffffffffff +#elif defined(__linux__) +# define TOOBIG 0x0fffffff +#elif defined(GIT_ARCH_64) +# define TOOBIG 0xffffffffffffff00 #else -#define TOOBIG 0xffffff00 +# define TOOBIG 0xffffff00 #endif /** diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index c3475f411..e93dc1483 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -1086,6 +1086,8 @@ void test_checkout_tree__filemode_preserved_in_workdir(void) cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); git_commit_free(commit); +#else + cl_skip(); #endif } diff --git a/tests/clar.c b/tests/clar.c index 905d67db7..27d35e1c7 100644 --- a/tests/clar.c +++ b/tests/clar.c @@ -95,9 +95,6 @@ static const char * fixture_path(const char *base, const char *fixture_name); struct clar_error { - const char *test; - int test_number; - const char *suite; const char *file; int line_number; const char *error_msg; @@ -106,11 +103,34 @@ struct clar_error { struct clar_error *next; }; -static struct { - int argc; - char **argv; +struct clar_explicit { + size_t suite_idx; + const char *filter; + + struct clar_explicit *next; +}; + +struct clar_report { + const char *test; + int test_number; + const char *suite; + + enum cl_test_status status; + + struct clar_error *errors; + struct clar_error *last_error; + + struct clar_report *next; +}; + +struct clar_summary { + const char *filename; + FILE *fp; +}; +static struct { enum cl_test_status test_status; + const char *active_test; const char *active_suite; @@ -124,8 +144,15 @@ static struct { int exit_on_error; int report_suite_names; - struct clar_error *errors; - struct clar_error *last_error; + int write_summary; + const char *summary_filename; + struct clar_summary *summary; + + struct clar_explicit *explicit; + struct clar_explicit *last_explicit; + + struct clar_report *reports; + struct clar_report *last_report; void (*local_cleanup)(void *); void *local_cleanup_payload; @@ -155,7 +182,7 @@ struct clar_suite { /* From clar_print_*.c */ static void clar_print_init(int test_count, int suite_count, const char *suite_names); static void clar_print_shutdown(int test_count, int suite_count, int error_count); -static void clar_print_error(int num, const struct clar_error *error); +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error); static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed); static void clar_print_onsuite(const char *suite_name, int suite_index); static void clar_print_onabort(const char *msg, ...); @@ -164,6 +191,10 @@ static void clar_print_onabort(const char *msg, ...); static void clar_unsandbox(void); static int clar_sandbox(void); +/* From summary.h */ +static struct clar_summary *clar_summary_init(const char *filename); +static int clar_summary_shutdown(struct clar_summary *fp); + /* Load the declarations for the test suite */ #include "clar.suite" @@ -186,21 +217,29 @@ void cl_trace_register(cl_trace_cb *cb, void *payload) /* Core test functions */ static void -clar_report_errors(void) +clar_report_errors(struct clar_report *report) { + struct clar_error *error; int i = 1; - struct clar_error *error, *next; - - error = _clar.errors; - while (error != NULL) { - next = error->next; - clar_print_error(i++, error); - free(error->description); - free(error); - error = next; - } - _clar.errors = _clar.last_error = NULL; + for (error = report->errors; error; error = error->next) + clar_print_error(i++, _clar.last_report, error); +} + +static void +clar_report_all(void) +{ + struct clar_report *report; + struct clar_error *error; + int i = 1; + + for (report = _clar.reports; report; report = report->next) { + if (report->status != CL_TEST_FAILURE) + continue; + + for (error = report->errors; error; error = error->next) + clar_print_error(i++, report, error); + } } static void @@ -209,7 +248,6 @@ clar_run_test( const struct clar_func *initialize, const struct clar_func *cleanup) { - _clar.test_status = CL_TEST_OK; _clar.trampoline_enabled = 1; CL_TRACE(CL_TRACE__TEST__BEGIN); @@ -225,6 +263,9 @@ clar_run_test( _clar.trampoline_enabled = 0; + if (_clar.last_report->status == CL_TEST_NOTRUN) + _clar.last_report->status = CL_TEST_OK; + if (_clar.local_cleanup != NULL) _clar.local_cleanup(_clar.local_cleanup_payload); @@ -240,9 +281,9 @@ clar_run_test( _clar.local_cleanup_payload = NULL; if (_clar.report_errors_only) { - clar_report_errors(); + clar_report_errors(_clar.last_report); } else { - clar_print_ontest(test->name, _clar.tests_ran, _clar.test_status); + clar_print_ontest(test->name, _clar.tests_ran, _clar.last_report->status); } } @@ -251,6 +292,7 @@ clar_run_suite(const struct clar_suite *suite, const char *filter) { const struct clar_func *test = suite->tests; size_t i, matchlen; + struct clar_report *report; if (!suite->enabled) return; @@ -283,6 +325,21 @@ clar_run_suite(const struct clar_suite *suite, const char *filter) continue; _clar.active_test = test[i].name; + + report = calloc(1, sizeof(struct clar_report)); + report->suite = _clar.active_suite; + report->test = _clar.active_test; + report->test_number = _clar.tests_ran; + report->status = CL_TEST_NOTRUN; + + if (_clar.reports == NULL) + _clar.reports = report; + + if (_clar.last_report != NULL) + _clar.last_report->next = report; + + _clar.last_report = report; + clar_run_test(&test[i], &suite->initialize, &suite->cleanup); if (_clar.exit_on_error && _clar.total_errors) @@ -298,13 +355,14 @@ clar_usage(const char *arg) { printf("Usage: %s [options]\n\n", arg); printf("Options:\n"); - printf(" -sname\tRun only the suite with `name` (can go to individual test name)\n"); - printf(" -iname\tInclude the suite with `name`\n"); - printf(" -xname\tExclude the suite with `name`\n"); - printf(" -v \tIncrease verbosity (show suite names)\n"); - printf(" -q \tOnly report tests that had an error\n"); - printf(" -Q \tQuit as soon as a test fails\n"); - printf(" -l \tPrint suite names\n"); + printf(" -sname Run only the suite with `name` (can go to individual test name)\n"); + printf(" -iname Include the suite with `name`\n"); + printf(" -xname Exclude the suite with `name`\n"); + printf(" -v Increase verbosity (show suite names)\n"); + printf(" -q Only report tests that had an error\n"); + printf(" -Q Quit as soon as a test fails\n"); + printf(" -l Print suite names\n"); + printf(" -r[filename] Write summary file (to the optional filename)\n"); exit(-1); } @@ -313,11 +371,18 @@ clar_parse_args(int argc, char **argv) { int i; + /* Verify options before execute */ for (i = 1; i < argc; ++i) { char *argument = argv[i]; - if (argument[0] != '-') + if (argument[0] != '-' || argument[1] == '\0' + || strchr("sixvqQlr", argument[1]) == NULL) { clar_usage(argv[0]); + } + } + + for (i = 1; i < argc; ++i) { + char *argument = argv[i]; switch (argument[1]) { case 's': @@ -352,7 +417,24 @@ clar_parse_args(int argc, char **argv) _clar.report_suite_names = 1; switch (action) { - case 's': _clar_suites[j].enabled = 1; clar_run_suite(&_clar_suites[j], argument); break; + case 's': { + struct clar_explicit *explicit = + calloc(1, sizeof(struct clar_explicit)); + assert(explicit); + + explicit->suite_idx = j; + explicit->filter = argument; + + if (_clar.explicit == NULL) + _clar.explicit = explicit; + + if (_clar.last_explicit != NULL) + _clar.last_explicit->next = explicit; + + _clar_suites[j].enabled = 1; + _clar.last_explicit = explicit; + break; + } case 'i': _clar_suites[j].enabled = 1; break; case 'x': _clar_suites[j].enabled = 0; break; } @@ -390,8 +472,14 @@ clar_parse_args(int argc, char **argv) _clar.report_suite_names = 1; break; + case 'r': + _clar.write_summary = 1; + _clar.summary_filename = *(argument + 2) ? (argument + 2) : + "summary.xml"; + break; + default: - clar_usage(argv[0]); + assert(!"Unexpected commandline argument!"); } } } @@ -405,23 +493,31 @@ clar_test_init(int argc, char **argv) "" ); + if (argc > 1) + clar_parse_args(argc, argv); + + if (_clar.write_summary && + !(_clar.summary = clar_summary_init(_clar.summary_filename))) { + clar_print_onabort("Failed to open the summary file\n"); + exit(-1); + } + if (clar_sandbox() < 0) { clar_print_onabort("Failed to sandbox the test runner.\n"); exit(-1); } - - _clar.argc = argc; - _clar.argv = argv; } int clar_test_run(void) { - if (_clar.argc > 1) - clar_parse_args(_clar.argc, _clar.argv); + size_t i; + struct clar_explicit *explicit; - if (!_clar.suites_ran) { - size_t i; + if (_clar.explicit) { + for (explicit = _clar.explicit; explicit; explicit = explicit->next) + clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter); + } else { for (i = 0; i < _clar_suite_count; ++i) clar_run_suite(&_clar_suites[i], NULL); } @@ -432,6 +528,9 @@ clar_test_run(void) void clar_test_shutdown(void) { + struct clar_explicit *explicit, *explicit_next; + struct clar_report *report, *report_next; + clar_print_shutdown( _clar.tests_ran, (int)_clar_suite_count, @@ -439,6 +538,21 @@ clar_test_shutdown(void) ); clar_unsandbox(); + + if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) { + clar_print_onabort("Failed to write the summary file\n"); + exit(-1); + } + + for (explicit = _clar.explicit; explicit; explicit = explicit_next) { + explicit_next = explicit->next; + free(explicit); + } + + for (report = _clar.reports; report; report = report_next) { + report_next = report->next; + free(report); + } } int @@ -458,7 +572,7 @@ static void abort_test(void) if (!_clar.trampoline_enabled) { clar_print_onabort( "Fatal error: a cleanup method raised an exception."); - clar_report_errors(); + clar_report_errors(_clar.last_report); exit(-1); } @@ -468,7 +582,7 @@ static void abort_test(void) void clar__skip(void) { - _clar.test_status = CL_TEST_SKIP; + _clar.last_report->status = CL_TEST_SKIP; _clar.total_skipped++; abort_test(); } @@ -482,17 +596,14 @@ void clar__fail( { struct clar_error *error = calloc(1, sizeof(struct clar_error)); - if (_clar.errors == NULL) - _clar.errors = error; + if (_clar.last_report->errors == NULL) + _clar.last_report->errors = error; - if (_clar.last_error != NULL) - _clar.last_error->next = error; + if (_clar.last_report->last_error != NULL) + _clar.last_report->last_error->next = error; - _clar.last_error = error; + _clar.last_report->last_error = error; - error->test = _clar.active_test; - error->test_number = _clar.tests_ran; - error->suite = _clar.active_suite; error->file = file; error->line_number = line; error->error_msg = error_msg; @@ -501,7 +612,7 @@ void clar__fail( error->description = strdup(description); _clar.total_errors++; - _clar.test_status = CL_TEST_FAILURE; + _clar.last_report->status = CL_TEST_FAILURE; if (should_abort) abort_test(); @@ -646,3 +757,4 @@ void cl_set_cleanup(void (*cleanup)(void *), void *opaque) #include "clar/fixtures.h" #include "clar/fs.h" #include "clar/print.h" +#include "clar/summary.h" diff --git a/tests/clar.h b/tests/clar.h index 5c674d70f..bdaab09d7 100644 --- a/tests/clar.h +++ b/tests/clar.h @@ -12,13 +12,16 @@ enum cl_test_status { CL_TEST_OK, CL_TEST_FAILURE, - CL_TEST_SKIP + CL_TEST_SKIP, + CL_TEST_NOTRUN, }; +/** Setup clar environment */ void clar_test_init(int argc, char *argv[]); int clar_test_run(void); void clar_test_shutdown(void); +/** One shot setup & run */ int clar_test(int argc, char *argv[]); const char *clar_sandbox_path(void); diff --git a/tests/clar/print.h b/tests/clar/print.h index 6529b6b4c..40bdef8c7 100644 --- a/tests/clar/print.h +++ b/tests/clar/print.h @@ -13,16 +13,16 @@ static void clar_print_shutdown(int test_count, int suite_count, int error_count (void)error_count; printf("\n\n"); - clar_report_errors(); + clar_report_all(); } -static void clar_print_error(int num, const struct clar_error *error) +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error) { printf(" %d) Failure:\n", num); printf("%s::%s [%s:%d]\n", - error->suite, - error->test, + report->suite, + report->test, error->file, error->line_number); @@ -44,6 +44,7 @@ static void clar_print_ontest(const char *test_name, int test_number, enum cl_te case CL_TEST_OK: printf("."); break; case CL_TEST_FAILURE: printf("F"); break; case CL_TEST_SKIP: printf("S"); break; + case CL_TEST_NOTRUN: printf("N"); break; } fflush(stdout); diff --git a/tests/clar/summary.h b/tests/clar/summary.h new file mode 100644 index 000000000..1af110efa --- /dev/null +++ b/tests/clar/summary.h @@ -0,0 +1,134 @@ + +#include <stdio.h> +#include <time.h> + +int clar_summary_close_tag( + struct clar_summary *summary, const char *tag, int indent) +{ + const char *indt; + + if (indent == 0) indt = ""; + else if (indent == 1) indt = "\t"; + else indt = "\t\t"; + + return fprintf(summary->fp, "%s</%s>\n", indt, tag); +} + +int clar_summary_testsuites(struct clar_summary *summary) +{ + return fprintf(summary->fp, "<testsuites>\n"); +} + +int clar_summary_testsuite(struct clar_summary *summary, + int idn, const char *name, const char *pkg, time_t timestamp, + double elapsed, int test_count, int fail_count, int error_count) +{ + struct tm *tm = localtime(×tamp); + char iso_dt[20]; + + if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0) + return -1; + + return fprintf(summary->fp, "\t<testsuite " + " id=\"%d\"" + " name=\"%s\"" + " package=\"%s\"" + " hostname=\"localhost\"" + " timestamp=\"%s\"" + " time=\"%.2f\"" + " tests=\"%d\"" + " failures=\"%d\"" + " errors=\"%d\">\n", + idn, name, pkg, iso_dt, elapsed, test_count, fail_count, error_count); +} + +int clar_summary_testcase(struct clar_summary *summary, + const char *name, const char *classname, double elapsed) +{ + return fprintf(summary->fp, + "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n", + name, classname, elapsed); +} + +int clar_summary_failure(struct clar_summary *summary, + const char *type, const char *message, const char *desc) +{ + return fprintf(summary->fp, + "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n", + type, message, desc); +} + +struct clar_summary *clar_summary_init(const char *filename) +{ + struct clar_summary *summary; + FILE *fp; + + if ((fp = fopen(filename, "w")) == NULL) + return NULL; + + if ((summary = malloc(sizeof(struct clar_summary))) == NULL) { + fclose(fp); + return NULL; + } + + summary->filename = filename; + summary->fp = fp; + + return summary; +} + +int clar_summary_shutdown(struct clar_summary *summary) +{ + struct clar_report *report; + const char *last_suite = NULL; + + if (clar_summary_testsuites(summary) < 0) + goto on_error; + + report = _clar.reports; + while (report != NULL) { + struct clar_error *error = report->errors; + + if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) { + if (clar_summary_testsuite(summary, 0, report->suite, "", + time(NULL), 0, _clar.tests_ran, _clar.total_errors, 0) < 0) + goto on_error; + } + + last_suite = report->suite; + + clar_summary_testcase(summary, report->test, "what", 0); + + while (error != NULL) { + if (clar_summary_failure(summary, "assert", + error->error_msg, error->description) < 0) + goto on_error; + + error = error->next; + } + + if (clar_summary_close_tag(summary, "testcase", 2) < 0) + goto on_error; + + report = report->next; + + if (!report || strcmp(last_suite, report->suite) != 0) { + if (clar_summary_close_tag(summary, "testsuite", 1) < 0) + goto on_error; + } + } + + if (clar_summary_close_tag(summary, "testsuites", 0) < 0 || + fclose(summary->fp) != 0) + goto on_error; + + printf("written summary file to %s\n", summary->filename); + + free(summary); + return 0; + +on_error: + fclose(summary->fp); + free(summary); + return -1; +} diff --git a/tests/core/memmem.c b/tests/core/memmem.c new file mode 100644 index 000000000..fd9986d01 --- /dev/null +++ b/tests/core/memmem.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" + +static void assert_found(const char *haystack, const char *needle, size_t expected_pos) +{ + cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, + needle, needle ? strlen(needle) : 0), + haystack + expected_pos); +} + +static void assert_absent(const char *haystack, const char *needle) +{ + cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, + needle, needle ? strlen(needle) : 0), + NULL); +} + +void test_core_memmem__found(void) +{ + assert_found("a", "a", 0); + assert_found("ab", "a", 0); + assert_found("ba", "a", 1); + assert_found("aa", "a", 0); + assert_found("aab", "aa", 0); + assert_found("baa", "aa", 1); + assert_found("dabc", "abc", 1); + assert_found("abababc", "abc", 4); +} + +void test_core_memmem__absent(void) +{ + assert_absent("a", "b"); + assert_absent("a", "aa"); + assert_absent("ba", "ab"); + assert_absent("ba", "ab"); + assert_absent("abc", "abcd"); + assert_absent("abcabcabc", "bcac"); +} + +void test_core_memmem__edgecases(void) +{ + assert_absent(NULL, NULL); + assert_absent("a", NULL); + assert_absent(NULL, "a"); + assert_absent("", "a"); + assert_absent("a", ""); +} diff --git a/tests/core/strtol.c b/tests/core/strtol.c index 0d3b6a5e6..ba79fba51 100644 --- a/tests/core/strtol.c +++ b/tests/core/strtol.c @@ -1,45 +1,84 @@ #include "clar_libgit2.h" -void test_core_strtol__int32(void) +static void assert_l32_parses(const char *string, int32_t expected, int base) { int32_t i; + cl_git_pass(git__strntol32(&i, string, strlen(string), NULL, base)); + cl_assert_equal_i(i, expected); +} - cl_git_pass(git__strtol32(&i, "123", NULL, 10)); - cl_assert(i == 123); - cl_git_pass(git__strtol32(&i, " +123 ", NULL, 10)); - cl_assert(i == 123); - cl_git_pass(git__strtol32(&i, " +2147483647 ", NULL, 10)); - cl_assert(i == 2147483647); - cl_git_pass(git__strtol32(&i, " -2147483648 ", NULL, 10)); - cl_assert(i == -2147483648LL); - - cl_git_fail(git__strtol32(&i, " 2147483657 ", NULL, 10)); - cl_git_fail(git__strtol32(&i, " -2147483657 ", NULL, 10)); +static void assert_l32_fails(const char *string, int base) +{ + int32_t i; + cl_git_fail(git__strntol32(&i, string, strlen(string), NULL, base)); } -void test_core_strtol__int64(void) +static void assert_l64_parses(const char *string, int64_t expected, int base) { int64_t i; + cl_git_pass(git__strntol64(&i, string, strlen(string), NULL, base)); + cl_assert_equal_i(i, expected); +} + +static void assert_l64_fails(const char *string, int base) +{ + int64_t i; + cl_git_fail(git__strntol64(&i, string, strlen(string), NULL, base)); +} + +void test_core_strtol__int32(void) +{ + assert_l32_parses("123", 123, 10); + assert_l32_parses(" +123 ", 123, 10); + assert_l32_parses(" +2147483647 ", 2147483647, 10); + assert_l32_parses(" -2147483648 ", -2147483648LL, 10); + assert_l32_parses("A", 10, 16); + assert_l32_parses("1x1", 1, 10); - cl_git_pass(git__strtol64(&i, "123", NULL, 10)); - cl_assert(i == 123); - cl_git_pass(git__strtol64(&i, " +123 ", NULL, 10)); - cl_assert(i == 123); - cl_git_pass(git__strtol64(&i, " +2147483647 ", NULL, 10)); - cl_assert(i == 2147483647); - cl_git_pass(git__strtol64(&i, " -2147483648 ", NULL, 10)); - cl_assert(i == -2147483648LL); - cl_git_pass(git__strtol64(&i, " 2147483657 ", NULL, 10)); - cl_assert(i == 2147483657LL); - cl_git_pass(git__strtol64(&i, " -2147483657 ", NULL, 10)); - cl_assert(i == -2147483657LL); - cl_git_pass(git__strtol64(&i, " 9223372036854775807 ", NULL, 10)); - cl_assert(i == INT64_MAX); - cl_git_pass(git__strtol64(&i, " -9223372036854775808 ", NULL, 10)); - cl_assert(i == INT64_MIN); - cl_git_pass(git__strtol64(&i, " 0x7fffffffffffffff ", NULL, 16)); - cl_assert(i == INT64_MAX); - cl_git_pass(git__strtol64(&i, " -0x8000000000000000 ", NULL, 16)); - cl_assert(i == INT64_MIN); + assert_l32_fails("", 10); + assert_l32_fails("a", 10); + assert_l32_fails("x10x", 10); + assert_l32_fails(" 2147483657 ", 10); + assert_l32_fails(" -2147483657 ", 10); } +void test_core_strtol__int64(void) +{ + assert_l64_parses("123", 123, 10); + assert_l64_parses(" +123 ", 123, 10); + assert_l64_parses(" +2147483647 ", 2147483647, 10); + assert_l64_parses(" -2147483648 ", -2147483648LL, 10); + assert_l64_parses(" 2147483657 ", 2147483657LL, 10); + assert_l64_parses(" -2147483657 ", -2147483657LL, 10); + assert_l64_parses(" 9223372036854775807 ", INT64_MAX, 10); + assert_l64_parses(" -9223372036854775808 ", INT64_MIN, 10); + assert_l64_parses(" 0x7fffffffffffffff ", INT64_MAX, 16); + assert_l64_parses(" -0x8000000000000000 ", INT64_MIN, 16); + assert_l64_parses("1a", 26, 16); + assert_l64_parses("1A", 26, 16); + + assert_l64_fails("", 10); + assert_l64_fails("a", 10); + assert_l64_fails("x10x", 10); + assert_l64_fails("0x8000000000000000", 16); + assert_l64_fails("-0x8000000000000001", 16); +} + +void test_core_strtol__buffer_length_truncates(void) +{ + int32_t i32; + int64_t i64; + + cl_git_pass(git__strntol32(&i32, "11", 1, NULL, 10)); + cl_assert_equal_i(i32, 1); + + cl_git_pass(git__strntol64(&i64, "11", 1, NULL, 10)); + cl_assert_equal_i(i64, 1); +} + +void test_core_strtol__error_message_cuts_off(void) +{ + assert_l32_fails("2147483657foobar", 10); + cl_assert(strstr(giterr_last()->message, "2147483657") != NULL); + cl_assert(strstr(giterr_last()->message, "foobar") == NULL); +} diff --git a/tests/iterator/iterator_helpers.c b/tests/iterator/iterator_helpers.c index ae48fcd46..68d574126 100644 --- a/tests/iterator/iterator_helpers.c +++ b/tests/iterator/iterator_helpers.c @@ -51,8 +51,7 @@ void expect_iterator_items( cl_assert(entry->mode != GIT_FILEMODE_TREE); } - if (++count >= expected_flat) - break; + cl_assert(++count <= expected_flat); } assert_at_end(i, v); diff --git a/tests/iterator/workdir.c b/tests/iterator/workdir.c index f33fd98f1..198edc7e8 100644 --- a/tests/iterator/workdir.c +++ b/tests/iterator/workdir.c @@ -662,7 +662,7 @@ void test_iterator_workdir__filesystem_gunk(void) /* should only have 13 items, since we're not asking for trees to be * returned. the goal of this test is simply to not crash. */ - expect_iterator_items(i, 13, NULL, 13, NULL); + expect_iterator_items(i, 15, NULL, 15, NULL); git_iterator_free(i); git_buf_free(&parent); } @@ -741,6 +741,8 @@ void test_iterator_workdir__skips_fifos_and_special_files(void) cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); git_iterator_free(i); +#else + cl_skip(); #endif } diff --git a/tests/online/clone.c b/tests/online/clone.c index c5d2ab188..3fc6ddd03 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -263,8 +263,11 @@ static int cred_failure_cb( void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void) { - if (!_remote_url || !_remote_user) - clar__skip(); + git__free(_remote_url); + git__free(_remote_user); + + _remote_url = git__strdup("https://github.com/libgit2/non-existent"); + _remote_user = git__strdup("libgit2test"); g_options.fetch_opts.callbacks.credentials = cred_failure_cb; @@ -293,8 +296,11 @@ void test_online_clone__cred_callback_called_again_on_auth_failure(void) { size_t counter = 0; - if (!_remote_url || !_remote_user) - clar__skip(); + git__free(_remote_url); + git__free(_remote_user); + + _remote_url = git__strdup("https://github.com/libgit2/non-existent"); + _remote_user = git__strdup("libgit2test"); g_options.fetch_opts.callbacks.credentials = cred_count_calls_cb; g_options.fetch_opts.callbacks.payload = &counter; @@ -577,7 +583,7 @@ void test_online_clone__ssh_cert(void) if (!_remote_ssh_fingerprint) cl_skip(); - cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, "ssh://localhost/foo", "./foo", &g_options)); + cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options)); } static char *read_key_file(const char *path) @@ -707,25 +713,37 @@ static int proxy_creds(git_cred **out, const char *url, const char *username, un void test_online_clone__proxy_credentials_request(void) { + git_buf url = GIT_BUF_INIT; + if (!_remote_proxy_url || !_remote_proxy_user || !_remote_proxy_pass) cl_skip(); + cl_git_pass(git_buf_printf(&url, "http://%s/", _remote_proxy_url)); + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; - g_options.fetch_opts.proxy_opts.url = _remote_proxy_url; + g_options.fetch_opts.proxy_opts.url = url.ptr; g_options.fetch_opts.proxy_opts.credentials = proxy_creds; called_proxy_creds = 0; cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); cl_assert(called_proxy_creds); + + git_buf_free(&url); } void test_online_clone__proxy_credentials_in_url(void) { - if (!_remote_proxy_url) + git_buf url = GIT_BUF_INIT; + + if (!_remote_proxy_url || !_remote_proxy_user || !_remote_proxy_pass) cl_skip(); + cl_git_pass(git_buf_printf(&url, "http://%s:%s@%s/", _remote_proxy_user, _remote_proxy_pass, _remote_proxy_url)); + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; - g_options.fetch_opts.proxy_opts.url = _remote_proxy_url; + g_options.fetch_opts.proxy_opts.url = url.ptr; called_proxy_creds = 0; cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); cl_assert(called_proxy_creds == 0); + + git_buf_free(&url); } diff --git a/tests/online/push.c b/tests/online/push.c index f72b4f8cb..3b98278e0 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -152,8 +152,12 @@ static void do_verify_push_status(record_callbacks_data *data, const push_status git_buf_free(&msg); } - git_vector_foreach(actual, i, iter) - git__free(iter); + git_vector_foreach(actual, i, iter) { + push_status *s = (push_status *)iter; + git__free(s->ref); + git__free(s->msg); + git__free(s); + } git_vector_free(actual); } @@ -393,7 +397,7 @@ void test_online_push__initialize(void) } git_remote_disconnect(_remote); - git_vector_free(&delete_specs); + git_vector_free_deep(&delete_specs); /* Now that we've deleted everything, fetch from the remote */ memcpy(&fetch_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); diff --git a/tests/perf/merge.c b/tests/perf/merge.c index b2ef082eb..721902d63 100644 --- a/tests/perf/merge.c +++ b/tests/perf/merge.c @@ -25,20 +25,7 @@ #define ID_BRANCH_A "d853fb9f24e0fe63b3dce9fbc04fd9cfe17a030b" #define ID_BRANCH_B "1ce9ea3ba9b4fa666602d52a5281d41a482cc58b" - -void test_perf_merge__initialize(void) -{ -} - -void test_perf_merge__cleanup(void) -{ -} - void test_perf_merge__m1(void) { -#if 1 - cl_skip(); -#else perf__do_merge(SRC_REPO, "m1", ID_BRANCH_A, ID_BRANCH_B); -#endif } diff --git a/tests/repo/open.c b/tests/repo/open.c index 3239b6fec..ab36dd587 100644 --- a/tests/repo/open.c +++ b/tests/repo/open.c @@ -180,6 +180,8 @@ void test_repo_open__from_git_new_workdir(void) cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2)); git_repository_free(repo2); +#else + cl_skip(); #endif } diff --git a/tests/status/worktree.c b/tests/status/worktree.c index 1345dbfd2..79eece85a 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -1072,6 +1072,8 @@ void test_status_worktree__unreadable(void) cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); cl_assert_equal_i(0, counts.wrong_status_flags_count); cl_assert_equal_i(0, counts.wrong_sorted_path); +#else + cl_skip(); #endif } @@ -1106,6 +1108,8 @@ void test_status_worktree__unreadable_not_included(void) cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); cl_assert_equal_i(0, counts.wrong_status_flags_count); cl_assert_equal_i(0, counts.wrong_sorted_path); +#else + cl_skip(); #endif } |