diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..295a47844 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,67 @@ +# Use the specified image as the base +FROM mcr.microsoft.com/devcontainers/base:bullseye + +# Set environment variables, if any +# ENV MY_ENV_VAR=my_value + +# Run package updates and install packages +RUN apt-get update + + +CMD code --install-extension ms-mssql.mssql + +USER vscode +RUN curl https://run.spawn.cc/install | sh +ENV PATH="/home/vscode/.spawnctl/bin:$PATH" + +USER root +RUN apt-get update && apt-get install -y curl gnupg software-properties-common \ + && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ + && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list \ + && apt-get update \ + && apt-get install -y gh + +RUN apt-get update && \ + apt-get install -y mono-complete + +ENV TSQLTCERTPATH /var/tSQLtCertTemp +ENV TSQLTCERTPASSWORD P@ssw0rdP@ssw0rdP@ssw0rdP@ssw0rd +RUN mkdir -p $TSQLTCERTPATH && \ + chmod 777 $TSQLTCERTPATH + +# Add the Microsoft SQL Server repository +RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ + && curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/msprod.list + +RUN apt-get update +ENV ACCEPT_EULA=Y +RUN apt-get install -y mssql-tools unixodbc-dev +ENV PATH="$PATH:/opt/mssql-tools/bin" + +RUN apt-get install -y unzip + +RUN curl -L https://aka.ms/sqlpackage-linux -o sqlpackage.zip \ + && unzip sqlpackage.zip -d /opt/sqlpackage \ + && chmod +x /opt/sqlpackage/sqlpackage \ + && rm sqlpackage.zip +ENV PATH="$PATH:/opt/sqlpackage" + +RUN apt-get install -y wget apt-transport-https software-properties-common && \ + wget -q "https://packages.microsoft.com/config/debian/$(. /etc/os-release && echo $VERSION_ID)/packages-microsoft-prod.deb" -O packages-microsoft-prod.deb && \ + dpkg -i packages-microsoft-prod.deb && \ + apt-get update && \ + apt-get install -y powershell + +RUN pwsh -Command "Install-Module -Name SqlServer -Scope AllUsers -AllowClobber -Force" + +RUN pwsh -Command "Install-Module -Name Az -AllowClobber -Scope AllUsers -Force" +RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash +RUN curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64 \ + && chmod +x ./bicep \ + && mv ./bicep /usr/local/bin/bicep + +# Set the default shell to PowerShell +SHELL ["pwsh", "-Command"] + + diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..5f4b1512b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,69 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-in-docker +{ + "name": "Docker in Docker", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + //"image": "mcr.microsoft.com/devcontainers/base:bullseye", + //"image": "mcr.microsoft.com/mssql/server:2022-latest", + // "image": "mcr.microsoft.com/devcontainers/base:bullseye", + "build": { + // Path is relataive to the devcontainer.json file. + "dockerfile": "Dockerfile" + }, + + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "version": "latest", + "enableNonRootDocker": "true", + "moby": "true" + }, + // "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, + "ghcr.io/devcontainers/features/dotnet:1": {}, + "ghcr.io/devcontainers/features/git:1": {}, + // "ghcr.io/devcontainers/features/git-lfs:1": {}, + // "ghcr.io/devcontainers/features/github-cli:1": {}, + // "ghcr.io/devcontainers/features/go:1": {}, + // "ghcr.io/devcontainers/features/powershell:1": {}, + // "ghcr.io/devcontainers/features/python:1": {}, + }, + + // Use this environment variable if you need to bind mount your local source code into a new container. + "remoteEnv": { + "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [], + + "customizations": { + "vscode": { + "settings": { + "remote.autoForwardPorts": false + }, + "extensions": [ + "ms-mssql.mssql", + "redhat.vscode-yaml", + "ms-vscode.powershell", + "ms-azuretools.vscode-docker", + "ms-mssql.sql-bindings-vscode", + "ms-mssql.sql-database-projects-vscode", + "ms-mssql.data-workspace-vscode", + "github.vscode-github-actions", + "ms-dotnettools.csdevkit", + "ms-vscode.powershell", + "ms-azuretools.vscode-bicep", + ] + } + }, + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": ["/bin/sh", "-c", "whoami && pwd && chmod 777 ./.devcontainer/postCreateCommand.sh && ./.devcontainer/postCreateCommand.sh"], // + + // Use 'postStartCommand' to run commands after the container is created. + "postStartCommand": ["/bin/sh", "-c", "whoami && pwd && chmod 777 ./.devcontainer/postStartCommand.sh && ./.devcontainer/postStartCommand.sh"], // + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} + + diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh new file mode 100755 index 000000000..27d92af5e --- /dev/null +++ b/.devcontainer/postCreateCommand.sh @@ -0,0 +1,11 @@ +cd "$(dirname "$0")" + +docker --version +docker-compose -f sqlserver.yml up -d --force-recreate + +cd "$TSQLTCERTPATH" +openssl req -x509 -newkey rsa:4096 -keyout tSQLtOfficialSigningKey.key -out tSQLtOfficialSigningKey.crt -days 365 -nodes -subj "/CN=yourdomain.com" +openssl pkcs12 -export -out tSQLtOfficialSigningKey.pfx -inkey tSQLtOfficialSigningKey.key -in tSQLtOfficialSigningKey.crt -passout pass:"$TSQLTCERTPASSWORD" + + +# pwsh -File ../tSQLt/PrepareServer.ps1 \ No newline at end of file diff --git a/.devcontainer/postStartCommand.sh b/.devcontainer/postStartCommand.sh new file mode 100755 index 000000000..b505c56de --- /dev/null +++ b/.devcontainer/postStartCommand.sh @@ -0,0 +1,4 @@ +cd "$(dirname "$0")" + +docker --version +docker-compose -f sqlserver.yml up -d \ No newline at end of file diff --git a/.devcontainer/sqlserver.yml b/.devcontainer/sqlserver.yml new file mode 100644 index 000000000..f11f3d9ed --- /dev/null +++ b/.devcontainer/sqlserver.yml @@ -0,0 +1,9 @@ +version: "3" +services: + sql_server: + image: mcr.microsoft.com/mssql/server:2022-latest + ports: + - "1433:1433" + environment: + ACCEPT_EULA: 'Y' + MSSQL_SA_PASSWORD: 'P@ssw0rd' diff --git a/.devcontainer/tSQLtOfficialSigningKey.crt b/.devcontainer/tSQLtOfficialSigningKey.crt new file mode 100644 index 000000000..ea7d777b9 --- /dev/null +++ b/.devcontainer/tSQLtOfficialSigningKey.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFEzCCAvugAwIBAgIUA+19JzEdOm4/g23a0HCbrZuMPGMwDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwOeW91cmRvbWFpbi5jb20wHhcNMjMxMjAxMjAxMzA5WhcN +MjQxMTMwMjAxMzA5WjAZMRcwFQYDVQQDDA55b3VyZG9tYWluLmNvbTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAOss5YSKp/ARdrfH7M20JT6y8KvX0iIj +26oMM/JgeAvzNHt7CRnneVtJQpQTpfT0ugskolBV/XHGJ+3bOdnTsDiWea6dZ7zq +SOIB0T3R+dA3EDFNUaVysVvrKzLU/GZL9RsZpMyvNUoChcL7UyTp+RWvS0reFCtp +wpoLsrE0YCFckoDCFiKFivdLg/eESJyeR6W3fq+8XrlqZ2Vin+FbukH/eTC3aoUO +s9z25K6v8nDn4EwqgDGdPyyU4EqnFy5oxEWwd1YoGQjXbGXTVZ6L8nUySadr8yXh +DAc2Orsd0RN5r6vJJFDbxbab0mYayyq7ZfoGjKVbgUS2jZHMpDUhgnTjXD+oI7PV +TrIqnY0pRTzh8xVOgFQP8H5J9aIS/tZDvImItmZ9MR1w/PoXt4VHd7nR8NMG/68I +f9CXSj3Fza+bCw3az+PTd04BC14kxXMPfRahu2KB4TZJBOQeTRJaB0aq8Lo1hZE0 +R6Amds3ouMo0jRww1LJ2r7CLRRp0CXRn+xD3e1GjcFQrW4vKTjR+7fSzJmpbl0Gf +XvicJBxLUSx6fIr1eEzEIy5zeZi2gK+SZ8zwIoN1xLKnED2WY2fYupuuw1CsG+vP +61087mJc1WCZGaTP2iQgfp7/P+WApzbM4Rvtbem1K9Jtq/F+EHHVJOZXqs50/+Lu +cz8DgvBWV5sxAgMBAAGjUzBRMB0GA1UdDgQWBBTSgnxtHjMiZWC78QZOjshCcwjh +XTAfBgNVHSMEGDAWgBTSgnxtHjMiZWC78QZOjshCcwjhXTAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCLpIQhtjlyu1J4tVASpA4BhCVbjibDYZQF +xJ2Bbpkk/LcspV3Ekgij1aQBQNwoEPbU3uxog+xJr8hWuN5GmU2btCToeG2C7OKA +Zn7USHuPoFUV8FA1MAS4iFrQOnZykzgX611lyeE7/ooABwidPZoVcvoWb5U6Niel +PqDUoUrMg3gRIaG9zm4+009TDbcntfS/MtX6wrMKXlke01PQtLo3ohmti1KURXqI +7Hu9NouFGxsUT+o8bkip/CsbScc7Ief9ADAJGEw+TSu2mD+1f7zgPeNzrR0kKbxp +0Kr3OXUCophzWaO+oIEYzZMLXUpA9jVW5NUPcmW+shuT0IiK3WQJnpMBaGzs9XQT +8LtrJrD9uVq9HpAGnjTfzoMWpBkqeIaG772NDKq9Z8LG4JVo2gV9rGC9uysDcms0 +dGFt5AfFecM5nOqsPss4BjP85ZH7quzIhm57SVaMvuO3N2OvHyoJJDy4OQy7P/rq +fBrmjP//Jb/i/hcRdZt17LhRdU2tuESHNWReFn0wRNLXOjkgg+Nntfl7qOUfNC23 +///3xTr5UhfArr2HZn9uo7VrrF99mF4CJTmqwic1e5RC3knnNwwB7JR8/O0H5csL +qKy57PSAU+w0pCrw/a4qeX+IswRLvQ6myTKIiDDGAruN/HOYEsAW3kuSLOxYFMKh +9F7w2nGAcA== +-----END CERTIFICATE----- diff --git a/.devcontainer/tSQLtOfficialSigningKey.key b/.devcontainer/tSQLtOfficialSigningKey.key new file mode 100644 index 000000000..6adb524f8 --- /dev/null +++ b/.devcontainer/tSQLtOfficialSigningKey.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDrLOWEiqfwEXa3 +x+zNtCU+svCr19IiI9uqDDPyYHgL8zR7ewkZ53lbSUKUE6X09LoLJKJQVf1xxift +2znZ07A4lnmunWe86kjiAdE90fnQNxAxTVGlcrFb6ysy1PxmS/UbGaTMrzVKAoXC ++1Mk6fkVr0tK3hQracKaC7KxNGAhXJKAwhYihYr3S4P3hEicnkelt36vvF65amdl +Yp/hW7pB/3kwt2qFDrPc9uSur/Jw5+BMKoAxnT8slOBKpxcuaMRFsHdWKBkI12xl +01Wei/J1Mkmna/Ml4QwHNjq7HdETea+rySRQ28W2m9JmGssqu2X6BoylW4FEto2R +zKQ1IYJ041w/qCOz1U6yKp2NKUU84fMVToBUD/B+SfWiEv7WQ7yJiLZmfTEdcPz6 +F7eFR3e50fDTBv+vCH/Ql0o9xc2vmwsN2s/j03dOAQteJMVzD30WobtigeE2SQTk +Hk0SWgdGqvC6NYWRNEegJnbN6LjKNI0cMNSydq+wi0UadAl0Z/sQ93tRo3BUK1uL +yk40fu30syZqW5dBn174nCQcS1EsenyK9XhMxCMuc3mYtoCvkmfM8CKDdcSypxA9 +lmNn2LqbrsNQrBvrz+tdPO5iXNVgmRmkz9okIH6e/z/lgKc2zOEb7W3ptSvSbavx +fhBx1STmV6rOdP/i7nM/A4LwVlebMQIDAQABAoICAHdqfskEly4B0CSV8hqpxNfH +iQnZNyx6xMneFGqYrZxzzEx/NyneLfM6igIiz/xIYEfUPE9Kur0Q/6l5zjBbm3nA +ft05tDlrRcLtZVBc1NrzetduCW1XhdPE4FSZbeiL3VP7e4Er3oVMLpvJfniaDg8C +ydqkVd2NgHE0S3JLxBxsoz+aTh8NbHkR8Vq34x8UCBGP0VZu8jJ4YzRKFiEasMNe +dC1WlVInGxGylIBXe2X1eCF0209WJ4P9vL5rhgvcyv0QFCi9h6jDqpkx7KHrMhxs +gpXu7LJpSfBHix8EjuuyuI8kPbqJPiXxja6nIE78hVWmCbjVdBhuEgjcvuzzrQew +U9Ftq4+A5DHb1oBky86/9SHIYAFZQnZfc1izTMxdRcTPQB21hRq/88hi0fg+yk1D +eL9wcabt7PAhj205eYFgoZHYnV+Ep69natcILo2ckZqiCioLvKHk+h1AqTe8xgVf +TGPpq+s6ZBtJfvQ1ePTJBGc4/iPJ6pDJ6Ox87E9m7NYSYsvcPVxwOW0+AIQfmllE +U1bMZNCkzJHEa9BpwABUdUjTlwRuiZRQwfKbiMHmuf2tixumglqPapeXURTKXYd7 +7plP+wab/BPQw7ZG9MHTl5e4GpNsCn5okNHUijoArKLrtG5DzCGnPu1tbn2fbvPn +hIamTpp2ev0vHGRgIU+BAoIBAQD1hD5E8b111bOisQhlgox+AtT4THJIwFuOg9xh +zfcSgbi1X8P3AQaiVpjLrblS0JADDRMIsM3kPnq5ExGmhzhlVfhhkJtj2lUjPgsO +pVr+MX/PN6CVOvCis2MQJ+L6QThmEk6UtWluI7WZJDeLmd0RT+UGMLgj+4MZcptC +HY8T7L4RUb8q32DLJZ8yQ+Eqn7bzbywMCZy/i6zEm39esH1aCoNL7Nq3SOQnYzX4 +X3xvKPZQwyFfj5kN2C7taUDSwGV07AmVkJH7iibun4F+PUJiCu0PZU3DPUPCSdpv +C+LuI5u2HX8MXFe+fs1zSOMJ7Pn/n3eV3IK8H9LabcVMise5AoIBAQD1N5zwcTQM +0ubOYwwKLbbel8lVV7t9Nz8ic+jfuHuKJEpo3QUp5izFB6UUwVp3UXM23XNFnJi9 +krl4XdxnGs9x33elt6iMmwxpt12t4EqxV3kRxh1chyZjpxuFJhb5A/dNu5QHtoAx +OpDzIHqa3pEOLrxhFFkJOEHH2FMtm30NELNvFkbbw0WFvKzDgZ+/ldqtHCn1Ln3r +zScvmovNtwX1jbLZt9E2lASVLp9xUIevaJdz/qUjzuaopE1bWhiI0EWhifC9bb6c +zUgdqaUjQH3JkCVwCK4/2qt7H+d8qK2XdEDfBYEyjd0304PMh77vjf653Lw5pEUY +JNZxadLEdbs5AoIBAQDX7NRvAkRka+wuu4/s5uH0uTzgmysvvK1JK8MY6XHQfvIZ +4f2RMP/Kqmp7R3Bj2zWs2UTSMd0CTcjVGGY/lVEOHovyEqH3McAK3LgQy3C5eoMz +lXLPjCSykx4eyVWgTtfxPUyLEPmGejVuWCHZzxpT7XY4+YQZcUwdh2Pww9pf5ahL +xx9f71FLmOzn4jcXq5vVgQK2G4YWGBt8Vl2ww3ubQv+yOL8qzxqJYYHHzQK9KMNo +yBSD3OD5AhZTimwOblNMf4L2XU1mclxHJVVX1zyYyY8wKSI5AZqkNxu66jNMyVha +1Jo2Hsi0qwS5zXKP+jDsli0wSd1Y4owour340V1RAoIBAQCaBoWYbbLA/1QkfQhZ ++F7uMC8xYPsOar5IWMAEjm9i1Oq8NWAB8mdalzO09sJpQgr5gl3ecr9qDBhdp69C +QA/fOfuAZv0C3H0OhjPcfI7OF+CfLm7Q6L9PRu69UrCJV1lmRRV1Rsf8pAnZ/SOl +1XkNjIOdAtfuN5zx90o/0nPY2g2rEDYP4fbwSxPFdc0u3OTPaWpdbAeCLomXmB6K +Z5ThqVCW+qP6AT1jqzZRScEAh9Th8iguds5hTVqdr9jLTu79yZQimCVrfnH219E3 +dFHOYrFxTrKMv0CJBQA1SApJoKcCrwV5CKzUXRJjTTy8YdeHTUQZfLo9sR3VfR/7 +cOvZAoIBAGN4WxCHbWD0bpofe17oKeY9aTl5lr2qhqC1WO4vBvmI7XVlXOFShL3v +DBpQOYAa45o/85nWeF+6F2jroQymuPNcV4g9OzZCqg/XuHizM0JXaplpAnV458Ku +rZo1P3TnXBgKrLSpL7KloKICbo8aegvkj/FJNq0EqGweq/aMPZB69u9PBsMlGtIN +GviD8mvEOOQfVu+qy0gcD9x51SNH2GYVE2U9vdaxCtf+8dnU+JWsU9UC82I/HW8e +0vDyxq4CX/7TZRJsyFDZG8k/eutRIUx6+z9n7NSkGOC0tuv2MAkKZUYd+wXS1WTI +11bvPI4fB5GVVMDLhPFgNyqPTwE4oJg= +-----END PRIVATE KEY----- diff --git a/.devcontainer/tSQLtOfficialSigningKey.pfx b/.devcontainer/tSQLtOfficialSigningKey.pfx new file mode 100644 index 000000000..ac63dd1e0 Binary files /dev/null and b/.devcontainer/tSQLtOfficialSigningKey.pfx differ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..4fe067eee --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. +* @tsqlt-org/maintainers diff --git a/.github/workflows/aks_build_and_test.yml b/.github/workflows/aks_build_and_test.yml new file mode 100644 index 000000000..99be52e65 --- /dev/null +++ b/.github/workflows/aks_build_and_test.yml @@ -0,0 +1,492 @@ +# Builds and tests a database project (in this case tSQLt) +# +# AZURE: Before this workflow will run successfully you must do the following +# 1. Run the github action workflow, "cicd_environment_setup.yml" + +name: Build and Test tSQLt + +# Controls when the action will run. +on: + workflow_dispatch: + inputs: + projectName: + description: 'Project Name' + required: true + default: 'db_cicd_project' + msSqlVersions: + description: 'names of the k8s deployment files in JSON array format (e.g. ["mssql2008r2","mssql2012","mssql2014","mssql2016","mssql2017","mssql2019","linux2017","linux2019"])' + required: true + default: '["mssql2014","mssql2016","mssql2017","mssql2019"]' + fail-fast: + description: 'set to "false" to allow all matrix jobs to run, even if some fail' + required: true + default: 'true' + +jobs: +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + build-tSQLt: + name: build tSQLt + # if: ${{ false }} + + # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners + # Note: [https://kubernetes.io/docs/tasks/tools/install-kubectl/#before-you-begin] + # You must use a kubectl version that is within one minor version difference of your cluster. For example, a v1.2 client should work with v1.1, v1.2, and v1.3 master. Using the latest version of kubectl helps avoid unforeseen issues. + runs-on: windows-latest # Kubectl 1.20 + + env: + PFX_PATH: ${{ github.workspace }}\tSQLt\Build\tSQLtOfficialSigningKey.pfx + PFX_PASSWORD: ${{ secrets.PFX_PASSWORD }} + ARTIFACT_DIR: ${{ github.workspace }}\artifacts + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it. + # But this is misleading, because this variable is only really helpful as ${{ github.workspace }} + - name: Checkout self + uses: actions/checkout@v2 + with: + path: cicd + + - name: Checkout tSQLt + uses: actions/checkout@v2 + with: + repository: tSQLt-org/tSQLt +# ref: login-timeout-to-60 # OH BY THE WAY, #MDP --> order actually matters. If the ref does not appear here, checkout ignores it. No error. Very helpful. + path: tSQLt + token: ${{ secrets.my_pat }} + # Why do we need a token here? It's a public repository. + + # documentation: This is just what you need to do... + - name: create and install unofficial pfx file for signing dlls + shell: pwsh + run: | + $cert = New-SelfSignedCertificate -FriendlyName "tSQLt_testKey" -Type CodeSigningCert -Subject "CN=localhost" -KeyAlgorithm RSA -HashAlgorithm SHA1 -KeyLength 2048 -SmimeCapabilities -CertStoreLocation "cert:/LocalMachine" -Provider "Microsoft Strong Cryptographic Provider" + $pwd = ConvertTo-SecureString -String "$env:PFX_PASSWORD" -Force -AsPlainText + $pfxFile = Export-PfxCertificate -cert ("Cert:\LocalMachine\My\" + $cert.thumbprint) -FilePath $env:PFX_PATH -Password $pwd + &"${{ github.workspace }}\tSQLt\Build\SnInstallPfx" "$env:PFX_PATH" "$env:PFX_PASSWORD" tSQLt_OfficialSigningKey + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.2 + + - name: build tsqltCLR + shell: pwsh + run: | + cd "${{ github.workspace }}\tSQLt\tSQLtCLR" + msbuild .\tSQLtCLR.sln -m -p:Configuration=CruiseControl + + - name: copy files to artifact staging and compress to tSQLtCLR.zip + shell: pwsh + run: | + New-Item -ItemType Directory -Path "$env:ARTIFACT_DIR\tSQLtCLR" + New-Item -ItemType Directory -Path "${{ github.workspace }}\tSQLt\Build\output" + Get-Item -Path "${{ github.workspace }}\tSQLt\tSQLtCLR\*\bin\*\*" -Filter *.dll | Copy-Item -Destination "$env:ARTIFACT_DIR\tSQLtCLR" -Container:$false + $compress = @{ + Path = "$env:ARTIFACT_DIR\tSQLtCLR\*.dll" + CompressionLevel = "Fastest" + DestinationPath = "${{ github.workspace }}\tSQLt\Build\output\tSQLtCLR.zip" + } + Compress-Archive @compress + + - name: ant Build/tSQLt.build.xml + run: ant -buildfile "${{ github.workspace }}\tSQLt\Build\tSQLt.build.xml" all + + - name: copy tSQLt artifacts to ${{ env.artifact_dir }} + shell: pwsh + run: | + Remove-Item "$env:ARTIFACT_DIR" -Recurse + New-Item -ItemType Directory -Path "$env:ARTIFACT_DIR" + $basePath = "${{ github.workspace }}\tSQLt\Build\output\"; + $artifactFiles = (@("Version.txt","tSQLt.zip","tSQLt.tests.zip","tSQLtSnippets(SQLPrompt).zip")| ForEach-Object { $basePath + $_ }); + $artifactFiles | Copy-Item -Verbose -Destination "$env:ARTIFACT_DIR"; + + - name: Archive production artifact + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: tSQLtAndTests + path: "${{ env.artifact_dir }}\\" + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + set-up-k8s-env: + name: set up matrix variables + + runs-on: windows-latest # Kubectl 1.20 + + env: + PROJECT_NAME: ${{ github.event.inputs.projectName }} + MS_SQL_VERSIONS: ${{ github.event.inputs.msSqlVersions }} + SQL_PORT_MIN: ${{ secrets.SQL_PORT_MIN }} + SQL_PORT_MAX: ${{ secrets.SQL_PORT_MAX }} + AZ_SERVICE_PRINCIPAL_CREDENTIALS: ${{ secrets[format('AZ_SP_CRED_{0}', github.event.inputs.projectName)] }} + + steps: + + - name: create variables + id: create-vars + shell: pwsh + run: | + function Get-MD5HashOfString($string) { + $stringAsStream = [System.IO.MemoryStream]::new(); + $writer = [System.IO.StreamWriter]::new($stringAsStream); + $writer.write($string); + $writer.Flush(); + $stringAsStream.Position = 0; + $hashedString = (Get-FileHash -InputStream $stringAsStream).Hash; + return [String]$hashedString; + } + + $projectNameHash = (Get-MD5HashOfString($env:PROJECT_NAME)).Substring(0,10); + $azSecretsManagerName = "sm-" + $projectNameHash; + $aksNamespace = "ns-${{ github.run_id }}-${{ github.run_number }}"; + + $msSqlVersionArray = ($env:MS_SQL_VERSIONS|ConvertFrom-Json); + $msSqlVersionForMatrix = '{"mssqlversion":'+$env:MS_SQL_VERSIONS+'}'; + + $randomSqlPorts = $env:SQL_PORT_MIN..$env:SQL_PORT_MAX | Get-Random -Shuffle | Select-Object -First ($msSqlVersionArray.count) + $msSqlPorts = @{}; + $portNumber = 0; + $msSqlVersionArray|ForEach{$msSqlPorts += @{"$_"=$randomSqlPorts[$portNumber++]}} + $serializedPorts = ($msSqlPorts|ConvertTo-Json -compress); + + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Host ("projectName value: {0}" -f $env:PROJECT_NAME); + Write-Host ("serializedPorts: {0}" -f "$serializedPorts"); + Write-Host ("msSqlVersionForMatrix: {0}" -f "$msSqlVersionForMatrix"); + Write-Host ("azSecretsManagerName: {0}" -f "$azSecretsManagerName"); + Write-Host ("GitHub Run Id: {0}" -f "${{ github.run_id }}"); + Write-Host ("GitHub Run Number: {0}" -f "${{ github.run_number }}"); + Write-Host ("aksNamespace: {0}" -f "$aksNamespace"); + Write-Host ("aks namespaces must be no longer than 63 chars https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names"); + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + Write-Host ('::set-output name=namespace::'+$aksNamespace); + Write-Host ('::set-output name=ports::'+$serializedPorts); + Write-Host ('::set-output name=azsecretsmanagername::'+$azSecretsManagerName); + Write-Host ("::set-output name=msSqlVersionForMatrix::{0}" -f "$msSqlVersionForMatrix"); + + outputs: + mssqlversion: ${{ steps.create-vars.outputs.msSqlVersionForMatrix }} + namespace: ${{ steps.create-vars.outputs.namespace }} + ports: ${{ steps.create-vars.outputs.ports }} + azsecretsmanagername: ${{ steps.create-vars.outputs.azSecretsManagerName }} + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + test-tSQLt: + name: test build artifacts + needs: [build-tSQLt,set-up-k8s-env] + + # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners + # Note: [https://kubernetes.io/docs/tasks/tools/install-kubectl/#before-you-begin] + # You must use a kubectl version that is within one minor version difference of your cluster. For example, a v1.2 client should work with v1.1, v1.2, and v1.3 master. Using the latest version of kubectl helps avoid unforeseen issues. + runs-on: windows-latest + + env: + NAMESPACE: ${{ needs.set-up-k8s-env.outputs.namespace }} + AZ_SECRETS_MANAGER_NAME: ${{ needs.set-up-k8s-env.outputs.azsecretsmanagername }} + SA_PASSWORD: ${{ secrets.SA_PASSWORD }} + TEST_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\TestResults + AZ_SERVICE_PRINCIPAL_CREDENTIALS: ${{ secrets[format('AZ_SP_CRED_{0}', github.event.inputs.projectName)] }} + + strategy: + matrix: ${{fromJson(needs.set-up-k8s-env.outputs.mssqlversion)}} + fail-fast: ${{ fromJSON(github.event.inputs.fail-fast) }} +# max-parallel: 2 + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + + - name: Checkout self + uses: actions/checkout@v2 + with: + path: cicd + + - name: Checkout tSQLt + uses: actions/checkout@v2 + with: + repository: tSQLt-org/tSQLt + path: tSQLt + token: ${{ secrets.my_pat }} + + - name: load namespace variables into env + id: create-vars + shell: pwsh + run: | + $sqlPortArray = ('${{ needs.set-up-k8s-env.outputs.ports }}'|ConvertFrom-Json) ; + $mssqlversion; + $osType; + if("linux-2017" -match "^(?[^\d-]*)-(?.+$)"){$matches;}else{1/0};##TODO:START-HERE + if ('${{ matrix.mssqlversion }}' -match "linux") { + $osType = "linux"; + $mssqlversion = + } else { + $osType = "windows"; + $mssqlversion = '${{ matrix.mssqlversion }}'; + }; + # don't use this. It works but is longer and mostly unreadable: $osType = @{$true="linux";$false="windows"}['${{ matrix.mssqlversion }}' -match "linux"]; + $sqlPort = $sqlPortArray.${{ matrix.mssqlversion }} + $aksNamespaceName = "$env:NAMESPACE-${{ matrix.mssqlversion }}" + + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Host ("sqlPort value: {0}" -f $sqlPort); + Write-Host ("azSecretsManagerName: {0}" -f "$env:AZ_SECRETS_MANAGER_NAME"); + Write-Host ("namespace: {0}" -f "$aksNamespaceName"); + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + $sqlcmdPath = (Get-Command sqlcmd).Path.TrimEnd("\\SQLCMD.EXE"); + + Write-Host ('::set-output name=namespace::'+$aksNamespaceName); + Write-Host ('::set-output name=sqlPort::'+$sqlPort); + Write-Host ('::set-output name=sqlcmdPath::'+$sqlcmdPath); + Write-Host ('::set-output name=osType::'+$osType); + + - name: download tSQLtAndTests artifact + uses: actions/download-artifact@v4.1.7 + with: + name: tSQLtAndTests + path: "${{ github.workspace }}\\tSQLt\\Build_Artifact" + + - name: decode az sp cred + id: cred-decode + shell: pwsh + run: | + $decodedCreds = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("${{ env.AZ_SERVICE_PRINCIPAL_CREDENTIALS}}")) + Write-Host ('::set-output name=az_sp_creds::'+$decodedCreds); + + # documentation: https://github.com/azure/login#configure-azure-credentials + - name: login via az module + uses: azure/login@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + enable-AzPSSession: true + + # documentation: https://github.com/Azure/get-keyvault-secrets + - name: get azure secrets + # TODO: Consider deleting this task and getting these manually and setting them as secrets. + uses: Azure/get-keyvault-secrets@v1.0 + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' # Note that this task can be replaced with a similar pattern as setting the namespace to the env variables (above), but is also not secure. + with: + keyvault: ${{ env.AZ_SECRETS_MANAGER_NAME }} + secrets: 'azResourceGroupName, aksClusterName, containerRegistryName' # comma separated list of secret keys that need to be fetched from the Key Vault + id: azure-secrets + + # Set the target AKS cluster. + - name: set aks context + uses: Azure/aks-set-context@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + cluster-name: ${{ steps.azure-secrets.outputs.aksClusterName }} + resource-group: ${{ steps.azure-secrets.outputs.azResourceGroupName }} + + - name: create and set the aks namespace for this job + shell: pwsh + run: | + kubectl config current-context + kubectl get namespace + kubectl create namespace ${{ steps.create-vars.outputs.namespace }} + # MDP: set the namespace for kubectl so that you don't have to specify the namespace in all kubectl calls + kubectl config set-context --current --namespace=${{ steps.create-vars.outputs.namespace }} + + - name: apply deployment to aks + shell: pwsh + run: | + + $replaceMssqlversion = (Get-Content -Path ${{ github.workspace }}\cicd\aks\mssql.deployment.${{ steps.create-vars.outputs.osType }}-template.yml) -replace 'matrix.mssqlversion','${{ matrix.mssqlversion }}' + $replaceAcrURL = $replaceMssqlversion -replace 'containerRegistryName','${{ steps.azure-secrets.outputs.containerRegistryName }}.azurecr.io' + $replaceAcrURL | Set-Content -Path ${{ github.workspace }}\cicd\aks\${{ matrix.mssqlversion }}.deployment.yml + + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Get-Content -Path .\cicd\aks\${{ matrix.mssqlversion }}.deployment.yml + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + kubectl apply -f ${{ github.workspace }}\cicd\aks\${{ matrix.mssqlversion }}.deployment.yml + kubectl expose deployment ${{ matrix.mssqlversion }}-deployment --type=LoadBalancer --name=${{ matrix.mssqlversion }}-service --port=${{ steps.create-vars.outputs.sqlPort }} --target-port=1433 + kubectl wait pod --for=condition=Ready --all --timeout 600s # Experimental (https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#wait) + + - name: check service + shell: pwsh + timeout-minutes: 3 + run: | + $ipAddress = kubectl get services ${{ matrix.mssqlversion }}-service -o jsonpath="{.status.loadBalancer.ingress[0].ip}" + + while ([String]::IsNullOrWhiteSpace($ipAddress)) + { + Start-Sleep -Seconds 10 + $ipAddress = kubectl get services ${{ matrix.mssqlversion }}-service -o jsonpath="{.status.loadBalancer.ingress[0].ip}" + } + + # add environment variables + #---------------------------------------------------------------------------------------------------# + # IMPORTANT (and, you've got to be kidding me): # + # The space between the comma and the sqlPort is absolutely required to make the ANT Task work. # + #---------------------------------------------------------------------------------------------------# + $fqdnAndPort = $ipAddress + ", " + ${{ steps.create-vars.outputs.sqlPort }}; + #-----------------------------^---------------------------------------------------------------------# + #-----------------------------^---------------------------------------------------------------------# + + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Host ("fqdnAndPort value: {0}" -f $fqdnAndPort); + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + echo "fqdnAndPort=$fqdnAndPort" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: check sql server instance and update sa password + shell: pwsh + run: | + kubectl exec service/${{ matrix.mssqlversion }}-service -- powershell.exe -c 'Get-Service MSSQL*' + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Host "SQL Server Info"; + kubectl exec service/${{ matrix.mssqlversion }}-service -- sqlcmd -l 60 -Q "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" + Write-Host ""; + Write-Host "Update sa password"; + # ALTER LOGIN sa WITH PASSWORD='Welcome1!',CHECK_POLICY=OFF,CHECK_EXPIRATION=OFF,DEFAULT_DATABASE=tempdb; ALTER LOGIN sa ENABLE; + $sqlStatement = "ALTER LOGIN sa WITH PASSWORD='" + $env:SA_PASSWORD + "',CHECK_POLICY=OFF,CHECK_EXPIRATION=OFF,DEFAULT_DATABASE=tempdb; ALTER LOGIN sa ENABLE;"; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Output $sqlStatement + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + kubectl exec service/${{ matrix.mssqlversion }}-service -- sqlcmd -l 60 -Q "$sqlStatement" + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + - name: print sql version info + shell: pwsh + run: | + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Host Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "$env:fqdnAndPort" -Username "sa" -Password "$env:SA_PASSWORD" -As DataSet + $DS = Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "$env:fqdnAndPort" -Username "sa" -Password "$env:SA_PASSWORD" -As DataSet + + $DS.Tables[0].Rows | %{ echo "{ $($_['U']), $($_['T']), $($_['V']) }" } + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + # @SM --> TODO: jUnit results + - name: ant Build/tSQLt.validatebuild.xml + working-directory: ${{ github.workspace }}\tSQLt\Build + run: ant -buildfile tSQLt.validatebuild.xml all -D"db.server"="$env:fqdnAndPort" -D"db.name"=tSQLt_dev -D"db.login"="-U sa -P $env:SA_PASSWORD -l 180 -t 360" -D"sqlcmd.path"="${{ steps.create-vars.outputs.sqlcmdPath }}" + + - name: create junit artifact + shell: pwsh + run: | + $basePath = "${{ github.workspace }}"; + $basePath; + + $ArtifactPath = "$env:TEST_ARTIFACT_DIR\"; + $ArtifactPath; + + New-Item -Path $ArtifactPath -ItemType directory -Force + + (Get-ChildItem $basePath -recurse -include "*TestResults*.xml").FullName; + + (Get-ChildItem $basePath -recurse -include "*TestResults*.xml").FullName|Copy-Item -Destination $ArtifactPath -recurse -PassThru + Set-Content -Path ($ArtifactPath+"CommitId.txt") -Value "${{ github.sha }}" + + - name: archive junit test artifact + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: jUnitTests-${{ matrix.mssqlversion }} + path: "${{ env.test_artifact_dir }}\\" + + # - name: delete the aks namespace + # if: ${{ always() }} + # shell: pwsh + # run: | + # kubectl delete namespaces ${{ steps.create-vars.outputs.namespace }} + + + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + drop-aks-namespace: + name: monitoring + needs: set-up-k8s-env + if: ${{ always() }} + runs-on: windows-latest + env: + AZ_SERVICE_PRINCIPAL_CREDENTIALS: ${{ secrets[format('AZ_SP_CRED_{0}', github.event.inputs.projectName)] }} + AZ_SECRETS_MANAGER_NAME: ${{ needs.set-up-k8s-env.outputs.azsecretsmanagername }} + NAMESPACE: ${{ needs.set-up-k8s-env.outputs.namespace }} + steps: + - name: decode az sp cred + id: cred-decode + shell: pwsh + run: | + $decodedCreds = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("${{ env.AZ_SERVICE_PRINCIPAL_CREDENTIALS}}")) + Write-Host ('::set-output name=az_sp_creds::'+$decodedCreds); + # documentation: https://github.com/azure/login#configure-azure-credentials + - name: login via az module + uses: azure/login@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + enable-AzPSSession: true + + # documentation: https://github.com/Azure/get-keyvault-secrets + - name: get azure secrets + # TODO: Consider deleting this task and getting these manually and setting them as secrets. + uses: Azure/get-keyvault-secrets@v1.0 + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' # Note that this task can be replaced with a similar pattern as setting the namespace to the env variables (above), but is also not secure. + with: + keyvault: ${{ env.AZ_SECRETS_MANAGER_NAME }} + secrets: 'azResourceGroupName, aksClusterName' # comma separated list of secret keys that need to be fetched from the Key Vault + id: azure-secrets + + # Set the target AKS cluster. + - name: set aks context + uses: Azure/aks-set-context@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + cluster-name: ${{ steps.azure-secrets.outputs.aksClusterName }} + resource-group: ${{ steps.azure-secrets.outputs.azResourceGroupName }} + + - name: monitorin step + shell: pwsh + timeout-minutes: 15 + run: | + while($true) + { + kubectl describe nodes --all-namespaces + Start-Sleep 15; + } + + + + + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + # - task: PowerShell@2 + # name: TagArtifact + # inputs: + # targetType: 'inline' + # script: | + # $CheckIfGitOk = {param($isOk);if(-not $isOk){Write-Host "##vso[task.logissue type=error]git gailed!";throw "git failed!"}}; + + # $TagName = "$(SQLVersionEdition)_$(Build.BuildId)" + + # Set-Location "$(Pipeline.Workspace)\tSQLtArtifacts\"; + + # Write-Host ("ArtifactBranchName: {0}" -f "$(ArtifactBranchName)"); + # try{ + # git status + # git remote show origin + + # git switch "$(ArtifactBranchName)" + # $CheckIfGitOk.invoke($?); + + # git tag $TagName; + # $CheckIfGitOk.invoke($?); + + # git push --tags + # $CheckIfGitOk.invoke($?); + + # git status + # }catch{ + # throw "git failed. See prior errors."; + # } \ No newline at end of file diff --git a/.github/workflows/aks_build_container_images.yml b/.github/workflows/aks_build_container_images.yml new file mode 100644 index 000000000..87ee232b0 --- /dev/null +++ b/.github/workflows/aks_build_container_images.yml @@ -0,0 +1,294 @@ +# This workflow creates the ACR images for the different supported SQL Server versions. + +name: Build MSSQL Images + +on: + # schedule: + # # * is a special character in YAML so you have to quote this string + # # Run every sunday at 6:20 AM + # - cron: '20 6 * * 0' + workflow_dispatch: + inputs: + projectName: + description: 'Project Name' + required: true + default: 'test_20210224' + machineName: + description: 'Machine Name, cannot be more than 15 characters long, be entirely numeric, or contain the following characters: ` ~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : . singlequote " , < > / ?.' + required: true + default: 'w2019c1' + msSqlVersions: + description: 'names of the k8s deployment files in JSON array format (e.g. ["windows-2008r2","windows-2012","windows-2014","windows-2016","windows-2017","windows-2019"])' + required: true + default: '["windows-2014","windows-2016","windows-2017","windows-2019"]' + debugVM: + description: 'set to "true" to retain the VM for debugging purposes' + required: false + default: 'false' + + +jobs: + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + create-vm: + name: Create The VM + runs-on: windows-latest +# if: ${{ false }} + + env: + PROJECT_NAME: ${{ github.event.inputs.projectName }} + MACHINE_NAME: ${{ github.event.inputs.machineName }} + MS_SQL_VERSIONS: ${{ github.event.inputs.msSqlVersions }} + AZ_SERVICE_PRINCIPAL_CREDENTIALS: ${{ secrets[format('AZ_SP_CRED_{0}', github.event.inputs.projectName)] }} + + steps: + - name: Checkout self + uses: actions/checkout@v2 + with: + path: cicd + + - name: create variables + id: create-vars + shell: pwsh + run: | + function Get-MD5HashOfString($string) { + $stringAsStream = [System.IO.MemoryStream]::new(); + $writer = [System.IO.StreamWriter]::new($stringAsStream); + $writer.write($string); + $writer.Flush(); + $stringAsStream.Position = 0; + $hashedString = (Get-FileHash -InputStream $stringAsStream).Hash; + return [String]$hashedString; + } + + $projectNameHash = (Get-MD5HashOfString($env:PROJECT_NAME)).Substring(0,10); + $machineName = "$env:MACHINE_NAME"; + $machineRgName = "rg_$machineName"; + $azSecretsManagerName = "sm-" + $projectNameHash; + $containerRegistryURL = "crn" + $projectNameHash + ".azurecr.io"; + $repoURL = "${{ github.SERVER_URL }}/${{ github.REPOSITORY }}.git"; + $commitId = "${{ github.SHA }}" ; + $msSqlVersionForMatrix = '{"windows-version":'+$env:MS_SQL_VERSIONS+'}'; + + + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Host ("projectName value: {0}" -f $env:PROJECT_NAME); + Write-Host ("machineName value: {0}" -f $machineName); + Write-Host ("machineRgName value: {0}" -f $machineRgName); + Write-Host ("msSqlVersionForMatrix: {0}" -f "$msSqlVersionForMatrix"); + Write-Host ("azSecretsManagerName: {0}" -f "$azSecretsManagerName"); + Write-Host ("containerRegistryURL: {0}" -f "$containerRegistryURL"); + Write-Host ("repoURL: {0}" -f "$repoURL"); + Write-Host ("commitId: {0}" -f "$commitId"); + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + Write-Host ('::set-output name=azsecretsmanagername::'+$azSecretsManagerName); + Write-Host ('::set-output name=containerregistryurl::'+$containerRegistryURL); + Write-Host ('::set-output name=repoURL::'+$repoURL); + Write-Host ('::set-output name=machineName::'+$machineName); + Write-Host ('::set-output name=machineRgName::'+$machineRgName); + Write-Host ('::set-output name=commitId::'+$commitId); + Write-Host ("::set-output name=msSqlVersionForMatrix::{0}" -f "$msSqlVersionForMatrix"); + + - name: decode az sp cred + id: cred-decode + shell: pwsh + run: | + $decodedCreds = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("${{ env.AZ_SERVICE_PRINCIPAL_CREDENTIALS}}")) + Write-Host ('::set-output name=az_sp_creds::'+$decodedCreds); + + # documentation: https://github.com/azure/login#configure-azure-credentials + - name: login via az module + uses: azure/login@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + enable-AzPSSession: true + + # documentation: https://github.com/Azure/get-keyvault-secrets + - name: get azure secrets + id: azure-secrets + uses: Azure/get-keyvault-secrets@v1.0 + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' # Note that this task can be replaced with a similar pattern as setting the namespace to the env variables (above), but is also not secure. + with: + keyvault: ${{ steps.create-vars.outputs.azsecretsmanagername }} + secrets: 'azResourceGroupName' # comma separated list of secret keys that need to be fetched from the Key Vault + + - name: run azure powershell script + id: buildMachine-ps + uses: azure/powershell@v1 + with: + azpsversion: 'latest' + errorActionPreference: 'continue' + inlineScript: | + $Parameters = @{ + projectName = "$env:PROJECT_NAME"; + azSecretsManagerName = "${{ steps.create-vars.outputs.azsecretsmanagername }}"; + azResourceGroupName = "${{ steps.azure-secrets.outputs.azResourceGroupName }}"; + machineRgName = "${{ steps.create-vars.outputs.machineRgName }}"; + repoURL = "${{ steps.create-vars.outputs.repoURL }}"; + commitId = "${{ steps.create-vars.outputs.commitId }}"; + machineName = "${{ steps.create-vars.outputs.machineName }}"; + debugOn = $true; + }; + ./cicd/envSetup/createContainerBuildMachine.ps1 @Parameters; + + outputs: + mssqlversion: ${{ steps.create-vars.outputs.msSqlVersionForMatrix }} + containerregistryurl: ${{ steps.create-vars.outputs.containerregistryurl }} + azsecretsmanagername: ${{ steps.create-vars.outputs.azSecretsManagerName }} + machinergname: ${{ steps.create-vars.outputs.machineRgName }} + machinename: ${{ steps.create-vars.outputs.machineName }} + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + create-images: + name: create and publish the ACR images + needs: create-vm + + runs-on: windows-latest + + env: + AZ_CONTAINER_REGISTRY_URL: ${{ needs.create-vm.outputs.containerregistryurl }} + AZ_SECRETS_MANAGER_NAME: ${{ needs.create-vm.outputs.azsecretsmanagername }} + MACHINE_NAME: ${{ needs.create-vm.outputs.machinename }} + MACHINE_RG_NAME: ${{ needs.create-vm.outputs.machinergname }} + AZ_SERVICE_PRINCIPAL_CREDENTIALS: ${{ secrets[format('AZ_SP_CRED_{0}', github.event.inputs.projectName)] }} + + strategy: + matrix: ${{fromJson(needs.create-vm.outputs.mssqlversion)}} + max-parallel: 1 + + steps: + + - name: Checkout self + uses: actions/checkout@v2 + with: + path: cicd + + - name: build and push docker image + shell: pwsh + run: | + $mssqlversion = '${{ matrix.mssqlversion }}'; + + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Host ("mssqlversion: {0}" -f "$mssqlversion"); + Write-Host ("azContainerRegistryURL: {0}" -f "$env:AZ_CONTAINER_REGISTRY_URL"); + Write-Host ("azSecretsManagerName: {0}" -f "$env:AZ_SECRETS_MANAGER_NAME"); + Write-Host ("machineName: {0}" -f "$env:MACHINE_NAME"); + Write-Host ("machineRGName: {0}" -f "$env:MACHINE_RG_NAME"); + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + - name: decode az sp cred + id: cred-decode + shell: pwsh + run: | + $decodedCreds = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("${{ env.AZ_SERVICE_PRINCIPAL_CREDENTIALS}}")); + Write-Host ('::set-output name=az_sp_creds::'+$decodedCreds); + + # documentation: https://github.com/azure/login#configure-azure-credentials + - name: login via az module + uses: azure/login@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + enable-AzPSSession: true + + - name: build and push image script + id: build-and-push-image + uses: azure/powershell@v1 + with: + azpsversion: 'latest' + errorActionPreference: 'continue' + inlineScript: | + $builImageScriptPath = 'cicd\envSetup\buildAndPushImage.ps1'; + Invoke-AzVMRunCommand -ResourceGroupName $env:MACHINE_RG_NAME -VMName $env:MACHINE_NAME -CommandId 'RunPowerShellScript' -ScriptPath $builImageScriptPath -Parameter @{mssqlVersion = "${{ matrix.mssqlversion }}"; acrURL = "$env:AZ_CONTAINER_REGISTRY_URL"; azSpCrBase64 = "${{ env.AZ_SERVICE_PRINCIPAL_CREDENTIALS}}"; debugOnString = "$debugOn"} + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + prune-untagged-images: + name: prune images + needs: [create-vm, create-images] + if: ${{ always() }} + + runs-on: windows-latest + + env: + AZ_CONTAINER_REGISTRY_URL: ${{ needs.create-vm.outputs.containerregistryurl }} + AZ_SERVICE_PRINCIPAL_CREDENTIALS: ${{ secrets[format('AZ_SP_CRED_{0}', github.event.inputs.projectName)] }} + + steps: + - name: decode az sp cred + id: cred-decode + shell: pwsh + run: | + $decodedCreds = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("${{ env.AZ_SERVICE_PRINCIPAL_CREDENTIALS}}")) + Write-Host ('::set-output name=az_sp_creds::'+$decodedCreds); + + # documentation: https://github.com/azure/login#configure-azure-credentials + - name: login via az module + uses: azure/login@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + enable-AzPSSession: true + + - name: drop image script + id: dropBuildMachine-ps + uses: azure/powershell@v1 + with: + azpsversion: 'latest' + errorActionPreference: 'continue' + inlineScript: | + # Documented here: https://docs.microsoft.com/en-us/azure/container-registry/container-registry-delete#delete-all-untagged-images + + $registry = "$env:AZ_CONTAINER_REGISTRY_URL"; + $repository = "windows-mssql"; # https://github.com/distribution/distribution/blob/main/docs/spec/api.md#overview, must adhere to: [a-z0-9]+(?:[._-][a-z0-9]+)* + + az acr repository show-manifests --name $registry --repository $repository --query "[?tags[0]==null].digest" -o tsv | %{ az acr repository delete --name $registry --image $repository@$_ --yes } + + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + drop-vm: + name: drop the VM + needs: [create-vm, create-images] + if: ${{ always() }} + + runs-on: windows-latest + + env: + AZ_SERVICE_PRINCIPAL_CREDENTIALS: ${{ secrets[format('AZ_SP_CRED_{0}', github.event.inputs.projectName)] }} + MACHINE_RG_NAME: ${{ needs.create-vm.outputs.machinergname }} + + steps: + - name: check debug VM + shell: pwsh + if: ${{ github.event.inputs.debugVM == 'true' }} + run: | + Write-Host ("::error::✨ ✨ ✨TURN OFF THE VM WHEN YOU ARE DONE!✨ ✨ ✨"); + exit 1; + + - name: decode az sp cred + id: cred-decode + shell: pwsh + run: | + $decodedCreds = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("${{ env.AZ_SERVICE_PRINCIPAL_CREDENTIALS}}")) + Write-Host ('::set-output name=az_sp_creds::'+$decodedCreds); + + # documentation: https://github.com/azure/login#configure-azure-credentials + - name: login via az module + uses: azure/login@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + enable-AzPSSession: true + + - name: drop image build machine resource group script + id: dropBuildMachine-ps + uses: azure/powershell@v1 + with: + azpsversion: 'latest' + errorActionPreference: 'continue' + inlineScript: | + Remove-AzResourceGroup -Name "$env:MACHINE_RG_NAME" -Force ; + + diff --git a/.github/workflows/aks_cicd_environment_setup.yml b/.github/workflows/aks_cicd_environment_setup.yml new file mode 100644 index 000000000..408463c08 --- /dev/null +++ b/.github/workflows/aks_cicd_environment_setup.yml @@ -0,0 +1,78 @@ +# This is the workflow to setup the environment for Data Platform CI/CD pipelines +# +# AZURE: Before this workflow will run successfully you must do the following +# 1. Run manualPrep.ps1 in the Azure Portal and follow the instructions to set up the AZ_SP_CRED_ secret in GitHub (Settings > Secrets > "New repository secret"). +# 2. Set up the SSH_PASSPHRASE secret in Settings > Secrets > "New repository secret" + +name: Set Up CI/CD Environment + +on: + workflow_dispatch: + inputs: + projectName: + description: 'Project Name' + required: true + default: 'db_cicd_project' + linuxNodePoolDefaultVMSize: + description: 'Linux Node Pool Default VM Size' + required: true + default: 'Standard_D2_v2' + windowsNodePoolDefaultVMSize: + description: 'Windows Node Pool Default VM Size' + required: true + default: 'Standard_D3_v2' + kubernetesVersion: + description: 'Kubernetes Version' + required: true + default: '1.19.6' + +jobs: + build: + name: Setup Azure Environment + runs-on: ubuntu-latest + + env: + PROJECT_NAME: ${{ github.event.inputs.projectName }} + LINUX_NODEPOOL_DEFAULT_VM_SIZE: ${{ github.event.inputs.linuxNodePoolDefaultVMSize }} + WINDOWS_NODEPOOL_DEFAULT_VM_SIZE: ${{ github.event.inputs.windowsNodePoolDefaultVMSize }} + KUBERNETES_VERSION: ${{ github.event.inputs.kubernetesVersion }} + AZ_SERVICE_PRINCIPAL_CREDENTIALS: ${{ secrets[format('AZ_SP_CRED_{0}', github.event.inputs.projectName)] }} + SSH_PASSPHRASE: ${{ secrets.SSH_PASSPHRASE }} + + steps: + - uses: actions/checkout@v2 + + - name: decode az sp cred + id: cred-decode + shell: pwsh + run: | + $decodedCreds = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("${{ env.AZ_SERVICE_PRINCIPAL_CREDENTIALS}}")) + Write-Host ('::set-output name=az_sp_creds::'+$decodedCreds); + + # documentation: https://github.com/azure/login#configure-azure-credentials + # TODO: set up a service principal which has permission only on the resource group and associated resources it creates + # MDP: This task can be replaced with Connect-AzAccount via service principal (https://docs.microsoft.com/en-us/powershell/azure/authenticate-azureps?view=azps-5.3.0). However for most pipelines, which would use azure powershell more than once, this is an anti-pattern. + # TODO: measure difference in performance between using this task and Connect-AzAccount in the next task + - name: login via az module + uses: azure/login@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + enable-azpssession: true + + # documentation: https://github.com/marketplace/actions/azure-powershell-action + - name: run azure powershell script + uses: azure/powershell@v1 + with: + azpsversion: 'latest' + errorActionPreference: 'continue' + inlineScript: | + $Parameters = @{ + projectName = "$env:PROJECT_NAME"; + azServicePrincipalCredentials = '${{ steps.cred-decode.outputs.az_sp_creds }}'; + sshPassphrase = "$env:SSH_PASSPHRASE"; + linuxNodePoolDefaultVMSize = "$env:LINUX_NODEPOOL_DEFAULT_VM_SIZE" + windowsNodePoolDefaultVMSize = "$env:WINDOWS_NODEPOOL_DEFAULT_VM_SIZE" + kubernetesVersion = "$env:KUBERNETES_VERSION" + debugOn = $false; + }; + ./envSetup/setup.ps1 @Parameters; diff --git a/.github/workflows/aks_manage_cluster.yml b/.github/workflows/aks_manage_cluster.yml new file mode 100644 index 000000000..fc96c2a18 --- /dev/null +++ b/.github/workflows/aks_manage_cluster.yml @@ -0,0 +1,155 @@ +name: Manage AKS + +on: + # schedule: + # # * is a special character in YAML so you have to quote this string + # # Run every sunday at 6:20 AM + # - cron: '15 5 * * *' + # repository_dispatch: + # types: [aks-start, aks-stop] + workflow_dispatch: + inputs: + projectName: + description: 'Project Name' + required: true + default: 'test_20210224' + aks_action: + description: 'AKS Action: start or stop' + required: true + default: 'stop' + +jobs: + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + aks-action: + name: Apply Action to AKS + runs-on: windows-latest + if: ${{ github.event.inputs.aks_action == 'start' || github.event.inputs.aks_action == 'stop' || github.event_name != 'workflow_dispatch'}} + + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_EVENT_ACTION: ${{ github.event.action }} + WORKFLOW_PROJECT_NAME: ${{ github.event.inputs.projectName }} + WORKFLOW_ACTION: ${{ github.event.inputs.aks_action }} + DEFAULT_PROJECT_NAME: test_20210224 + DEFAULT_ACTION: stop + + steps: + - name: create variables + id: create-vars + shell: pwsh + run: | + function Get-MD5HashOfString($string) { + $stringAsStream = [System.IO.MemoryStream]::new(); + $writer = [System.IO.StreamWriter]::new($stringAsStream); + $writer.write($string); + $writer.Flush(); + $stringAsStream.Position = 0; + $hashedString = (Get-FileHash -InputStream $stringAsStream).Hash; + return [String]$hashedString; + } + + $projectName; + $action; + if ($env:GITHUB_EVENT_NAME -ne "workflow_dispatch") { + $projectName = "$env:DEFAULT_PROJECT_NAME"; + if($env:GITHUB_EVENT_NAME -eq "repository_dispatch"){ + $env:GITHUB_EVENT_ACTION -match "aks-(?.*)" + $action = $matches.Command; + } else { + $action = "$env:DEFAULT_ACTION"; + } + } else { + $projectName = "$env:WORKFLOW_PROJECT_NAME"; + $action = "$env:WORKFLOW_ACTION"; + } + + $projectNameHash = (Get-MD5HashOfString($projectName)).Substring(0,10); + $azSecretsManagerName = "sm-" + $projectNameHash; + + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Host ("projectName value: {0}" -f $projectName); + Write-Host ("action value: {0}" -f $action); + Write-Host ("azSecretsManagerName: {0}" -f "$azSecretsManagerName"); + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + Write-Host ('::set-output name=azsecretsmanagername::'+$azSecretsManagerName); + Write-Host ('::set-output name=projectName::'+$projectName); + Write-Host ('::set-output name=action::'+$action); + + - name: decode az sp cred + id: cred-decode + env: + AZ_SERVICE_PRINCIPAL_CREDENTIALS: ${{ secrets[format('AZ_SP_CRED_{0}', steps.create-vars.outputs.projectName)] }} + shell: pwsh + run: | + $decodedCreds = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("${{ env.AZ_SERVICE_PRINCIPAL_CREDENTIALS}}")) + Write-Host ('::set-output name=az_sp_creds::'+$decodedCreds); + + # documentation: https://github.com/azure/login#configure-azure-credentials + - name: login via az module + uses: azure/login@v1 + with: + creds: ${{ steps.cred-decode.outputs.az_sp_creds }} + enable-AzPSSession: true + + # documentation: https://github.com/Azure/get-keyvault-secrets + - name: get azure secrets + id: azure-secrets + uses: Azure/get-keyvault-secrets@v1.0 + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' # Note that this task can be replaced with a similar pattern as setting the namespace to the env variables (above), but is also not secure. + with: + keyvault: ${{ steps.create-vars.outputs.azsecretsmanagername }} + secrets: 'azResourceGroupName,aksClusterName' # comma separated list of secret keys that need to be fetched from the Key Vault + + - name: aks action + id: aks-action + uses: azure/powershell@v1 + with: + azpsversion: 'latest' + errorActionPreference: 'continue' + inlineScript: | + az aks ${{ steps.create-vars.outputs.action }} --name ${{ steps.azure-secrets.outputs.aksClusterName }} --resource-group ${{ steps.azure-secrets.outputs.azResourceGroupName }} + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + unknown-aks-action: + name: Unknown Action for AKS + needs: [aks-action] + runs-on: windows-latest + if: ${{ always() && needs.aks-action.result == 'skipped' }} #MDPOMG, how to run a job if the previous one is skipped, but not if it isn't. + + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJSON(github) }} + run: echo "$GITHUB_CONTEXT" + + - name: Dump job context + env: + JOB_CONTEXT: ${{ toJSON(job) }} + run: echo "$JOB_CONTEXT" + + - name: Dump steps context + env: + STEPS_CONTEXT: ${{ toJSON(steps) }} + run: echo "$STEPS_CONTEXT" + + - name: Dump needs context + env: + NEEDS_CONTEXT: ${{ toJSON(needs) }} + run: echo "$NEEDS_CONTEXT" + + - name: Dump needs context + env: + NEEDS_CONTEXT: ${{ toJSON(needs.aks-action) }} + run: echo "$NEEDS_CONTEXT" + + - name: unknown action error + shell: pwsh + run: | + Write-Host ("::error::✨ ✨ ✨ `"${{ needs.aks-action.result }}`" is not a valid option. ✨ ✨ ✨"); + Write-Host ("::error::✨ ✨ ✨ `"stop`" and `"start`" are the only options! ✨ ✨ ✨"); + exit 1; diff --git a/.github/workflows/build_and_test_on_spawn.yml b/.github/workflows/build_and_test_on_spawn.yml new file mode 100644 index 000000000..c87d6fa45 --- /dev/null +++ b/.github/workflows/build_and_test_on_spawn.yml @@ -0,0 +1,392 @@ +# Builds and tests a database project (in this case tSQLt) +# +# Originally from: https://github.com/lizbaron/db-cicd-with-github-actions/blob/9a514ed0c41029daa231db22e30de7806636160c/.github/workflows/build_and_test_on_linux.yml + +name: Build and Test tSQLt on Spawn + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + spawnDataImages: + description: 'names of the data-images on spawn (e.g. all is currently equivalent to [["SQL2017","mssql-empty:v2017"],["SQL2019","mssql-empty:v2019"]] 2021-09-26)' + required: true + default: 'all' + +env: + MATRIX_NAME: 0 + MATRIX_SPAWNIMAGE: 1 + SPAWNDATAIMAGES_DEFAULT: + '[["SQL2017","mssql-empty:v2017"],["SQL2019","mssql-empty:v2019"]]' + SQLPACKAGE_HOME: 'C:\Program Files\Microsoft SQL Server\160\DAC\bin\' + +jobs: + + set-up-env-vars: + name: set up matrix variables + #if: ${{ always() }} + + # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners + runs-on: windows-latest + + env: + TSQLT_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\tSQLt + + steps: + + - name: create variables + id: create-vars + shell: pwsh + run: | + $spawnDataImages = '${{ github.event.inputs.spawnDataImages }}'; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + Write-Host ("input spawnDataImages: {0}" -f $spawnDataImages); + if ([string]::IsNullOrEmpty($spawnDataImages) -or ("all" -eq $spawnDataImages)) { + $spawnDataImages = '${{ env.spawndataimages_default }}'; + } + Write-Host ("final spawnDataImages value: {0}" -f $spawnDataImages); + $matrixValues = '{{"data-image":{0} }}' -f $spawnDataImages; + Write-Host ("matrixValues value: {0}" -f $matrixValues); + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + Write-Host ('::set-output name=matrixValues::' + $matrixValues); + + outputs: + matrixValues: ${{ steps.create-vars.outputs.matrixValues }} + + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + build-tSQLt: + name: build tSQLt + #if: ${{ false }} + + runs-on: windows-latest + + env: + PFX_PATH: ${{ github.workspace }}\tSQLt\Build\tSQLtOfficialSigningKey.pfx + ARTIFACT_DIR: ${{ github.workspace }}\artifacts + MSBUILD_HOME: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it. + # But this is misleading, because this variable is only really helpful as ${{ github.workspace }} + - name: Checkout tSQLt + uses: actions/checkout@v2 + with: + path: tSQLt + + # documentation: This is just what you need to do... + - name: create and install unofficial pfx file for signing dlls + shell: pwsh + run: | + $superSecurePassword = "ExtraSuperSecurePassword1!"; + $cert = New-SelfSignedCertificate -FriendlyName "tSQLt_testKey" -Type CodeSigningCert -Subject "CN=localhost" -KeyAlgorithm RSA -HashAlgorithm SHA1 -KeyLength 2048 -SmimeCapabilities -CertStoreLocation "cert:/LocalMachine" -Provider "Microsoft Strong Cryptographic Provider" + $pfxFile = Export-PfxCertificate -cert ("Cert:\LocalMachine\My\" + $cert.thumbprint) -FilePath $env:PFX_PATH -Password (ConvertTo-SecureString -String $superSecurePassword -Force -AsPlainText) + &"${{ github.workspace }}\tSQLt\Build\SnInstallPfx" "$env:PFX_PATH" $superSecurePassword tSQLt_OfficialSigningKey + + - name: ant Build\tSQLt.buildCLR.xml --> Build/output/CLRBuild/ + run: ant -buildfile "${{ github.workspace }}\tSQLt\Build\tSQLt.buildCLR.xml" -D"msbuild.path"="$env:MSBUILD_HOME" all + + - name: ant Build/tSQLt.build.xml --> Build/output/tSQLtBuild + run: ant -buildfile "${{ github.workspace }}\tSQLt\Build\tSQLt.build.xml" all -D"commit.id"="${{ github.sha }}" + + - name: copy tSQLt artifacts to ${{ env.artifact_dir }} + shell: pwsh + run: | + New-Item -ItemType Directory -Path "$env:ARTIFACT_DIR" + $basePath = "${{ github.workspace }}\tSQLt\Build\output\tSQLtBuild\"; + $artifactFiles = @("ReadMe.txt","CommitId.txt","CreateBuildLog.sql","GetFriendlySQLServerVersion.sql","tSQLt.tests.zip","tSQLtFacade.zip","tSQLtFiles.zip","tSQLtSnippets(SQLPrompt).zip","Version.txt"); + Get-ChildItem -Path ($basePath + "*") -Include $artifactFiles | Copy-Item -Destination "$env:ARTIFACT_DIR"; + + - name: Archive production artifact + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: tSQLtBuildArtifact + path: "${{ env.artifact_dir }}\\" + retention-days: 5 + + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + build-facades: + name: build facade dacpacs + needs: [build-tSQLt,set-up-env-vars] + #if: ${{ always() }} + + env: + DACPAC_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\DacpacBuild + + strategy: + matrix: ${{ fromJson(needs.set-up-env-vars.outputs.matrixValues) }} + fail-fast: true + + # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners + runs-on: windows-latest + + steps: + - name: Checkout tSQLt + uses: actions/checkout@v2 + with: + path: tSQLt + + - name: load namespace variables into env + id: create-vars + shell: pwsh + run: | + $sqlcmdPath = (Get-Command sqlcmd).Path.TrimEnd("\\SQLCMD.EXE"); + Write-Host ('::set-output name=sqlcmdPath::'+$sqlcmdPath); + + - name: Install spawnctl + shell: pwsh + run: | + echo "Downloading and installing spawnctl..." + Invoke-WebRequest -Uri "https://run.spawn.cc/spawnctl.exe" -UseBasicParsing -OutFile "${{ github.workspace }}\spawnctl.exe" + & "${{ github.workspace }}\spawnctl.exe" version + + - name: Create Container + id: create-container + shell: pwsh + run: | + & "${{ github.workspace }}\spawnctl.exe" get data-images + + $dataImageName = "${{ matrix.data-image[ env.MATRIX_SPAWNIMAGE ] }}"; + + echo "Creating 'tSQLtOnLinux' Spawn data container from image '$dataImageName'"; + $containerName= & "${{ github.workspace }}\spawnctl.exe" create data-container --image $dataImageName --lifetime 30m -q + $containerJSON= & "${{ github.workspace }}\spawnctl.exe" get data-container $containerName -o json + $containerInfo = ConvertFrom-JSON -inputObject $containerJSON; + $containerFQDN=$containerInfo.host+", "+$containerInfo.port; + $containerUser=$containerInfo.user; + $containerPassword=$containerInfo.password; + echo "Successfully created Spawn data container '$containerName'" + + Write-Host ('::set-output name=containerFQDN::'+$containerFQDN); + Write-Host ('::set-output name=containerUser::'+$containerUser); + Write-Host ('::set-output name=containerPassword::'+$containerPassword); + + env: + SPAWNCTL_ACCESS_TOKEN: ${{ secrets.SPAWNCTL_ACCESS_TOKEN }} + + - name: print sql version info + shell: pwsh + run: | + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + # Write-Host Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" -As DataSet + $DS = Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" -As DataSet + + $DS.Tables[0].Rows | %{ echo "{ $($_['U']), $($_['T']), $($_['V']) }" } + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + - name: download tSQLtBuildArtifact artifact + uses: actions/download-artifact@v4.1.7 + with: + name: tSQLtBuildArtifact + path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLtBuild" + + - name: build tSQLt and Facade Dacpacs --> Build/output/DacpacBuild/ + shell: pwsh + working-directory: ${{ github.workspace }}\tSQLt\ + run: | + .\Build\SetupDacpacBuild.ps1 -ErrorAction Stop + .\Build\FacadeBuildDacpac.ps1 -ErrorAction Stop -ServerName "${{ steps.create-container.outputs.containerFQDN }}" -DatabaseName "tSQLt_dev" -Login "-U ${{ steps.create-container.outputs.containerUser }} -P ${{ steps.create-container.outputs.containerPassword }}" -SqlCmdPath "${{ steps.create-vars.outputs.sqlcmdPath }}" -SqlPackagePath "$env:SQLPACKAGE_HOME" + .\Build\BuildtSQLtDacpac.ps1 -ErrorAction Stop -ServerName "${{ steps.create-container.outputs.containerFQDN }}" -DatabaseName "tSQLt_dev_dacpac_src" -Login "-U ${{ steps.create-container.outputs.containerUser }} -P ${{ steps.create-container.outputs.containerPassword }}" -SqlCmdPath "${{ steps.create-vars.outputs.sqlcmdPath }}" -SqlPackagePath "$env:SQLPACKAGE_HOME" + + - name: create dacpacs artifact + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: dacpacs + path: "${{ env.dacpac_artifact_dir }}\\" + retention-days: 5 + + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + create-artifact: + name: create artifact + needs: [build-facades] + #if: ${{ always() }} + + # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners + runs-on: windows-latest + + env: + TSQLT_PUBLIC_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\tSQLt\public + TSQLT_VALIDATION_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\tSQLt\validation + + steps: + - name: Checkout tSQLt + uses: actions/checkout@v2 + with: + path: tSQLt + + - name: download tSQLtBuildArtifact artifact + uses: actions/download-artifact@v4.1.7 + with: + name: tSQLtBuildArtifact + path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLtBuild" + + - name: download tSQLt dacpac artifact(s) + uses: actions/download-artifact@v4.1.7 + with: + name: dacpacs + path: "${{ github.workspace }}\\tSQLt\\Build\\output\\DacpacBuild" + + - name: inspect CreateArtifact directory + shell: pwsh + working-directory: ${{ github.workspace }}\tSQLt\Build\output + run: | + Get-ChildItem . -Recurse + + - name: package Build/output/tSQLt /public and /validation files + shell: pwsh + working-directory: ${{ github.workspace }}\tSQLt\ + run: | + .\Build\BuildtSQLtZip.ps1 -ErrorAction Stop + + - name: create tSQLt public artifact + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: tSQLtPublic + path: "${{ env.tsqlt_public_artifact_dir }}\\" + retention-days: 90 + + - name: create tSQLt validation artifact + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: tSQLtValidation + path: "${{ env.tsqlt_validation_artifact_dir }}\\" + retention-days: 5 + +# πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– + test-tSQLt-and-facade: + name: test build artifact + needs: [create-artifact,set-up-env-vars] + #if: ${{ always() }} + + env: + TEST_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\temp\Validate\TestResults + DACPAC_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\DacpacBuild + MATRIX_VERSIONS: ${{ format('{{"data-image":{0} }}',github.event.inputs.spawnDataImages) }} + + strategy: + matrix: ${{ fromJson(needs.set-up-env-vars.outputs.matrixValues) }} + fail-fast: false + + # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners + runs-on: windows-latest + + steps: + - name: Checkout tSQLt + uses: actions/checkout@v2 + with: + path: tSQLt + + - name: download public tSQLt artifact + uses: actions/download-artifact@v4.1.7 + with: + name: tSQLtPublic + path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLt\\public" + + - name: download validation tSQLt artifact + uses: actions/download-artifact@v4.1.7 + with: + name: tSQLtValidation + path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLt\\validation" + + - name: load namespace variables into env + id: create-vars + shell: pwsh + run: | + $sqlcmdPath = (Get-Command sqlcmd).Path.TrimEnd("\\SQLCMD.EXE"); + Write-Host ('::set-output name=sqlcmdPath::'+$sqlcmdPath); + + - name: Install spawnctl + shell: pwsh + run: | + echo "Downloading and installing spawnctl..." + Invoke-WebRequest -Uri "https://run.spawn.cc/spawnctl.exe" -UseBasicParsing -OutFile "${{ github.workspace }}\spawnctl.exe" + & "${{ github.workspace }}\spawnctl.exe" version + + - name: Create Container + id: create-container + shell: pwsh + run: | + & "${{ github.workspace }}\spawnctl.exe" get data-images + + $dataImageName = "${{ matrix.data-image[ env.MATRIX_SPAWNIMAGE] }}"; + + echo "Creating 'tSQLtOnLinux' Spawn data container from image '$dataImageName'"; + $containerName= & "${{ github.workspace }}\spawnctl.exe" create data-container --image $dataImageName --lifetime 30m -q + $containerJSON= & "${{ github.workspace }}\spawnctl.exe" get data-container $containerName -o json + $containerInfo = ConvertFrom-JSON -inputObject $containerJSON; + $containerFQDN=$containerInfo.host+", "+$containerInfo.port; + $containerUser=$containerInfo.user; + $containerPassword=$containerInfo.password; + echo "Successfully created Spawn data container '$containerName'" + + Write-Host ('::set-output name=containerFQDN::'+$containerFQDN); + Write-Host ('::set-output name=containerUser::'+$containerUser); + Write-Host ('::set-output name=containerPassword::'+$containerPassword); + + env: + SPAWNCTL_ACCESS_TOKEN: ${{ secrets.SPAWNCTL_ACCESS_TOKEN }} + + - name: print sql version info + shell: pwsh + run: | + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + # Write-Host Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" -As DataSet + $DS = Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" -As DataSet + + $DS.Tables[0].Rows | %{ echo "{ $($_['U']), $($_['T']), $($_['V']) }" } + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + - name: Setup SQL Users + shell: pwsh + run: | + $DS = Invoke-Sqlcmd -ConnectionTimeout 600 -InputFile "${{ github.workspace }}\tSQLt\Build\CreateLogin(tSQLt.Build).sql" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" + + # @SM --> TODO: jUnit results + - name: ant Build/tSQLt.validatebuild.xml + working-directory: ${{ github.workspace }}\tSQLt\Build + run: ant -buildfile tSQLt.validatebuild.xml all -D"db.server"="${{ steps.create-container.outputs.containerFQDN }}" -D"db.name"=tSQLt_dev -D"db.login"="-U ${{ steps.create-container.outputs.containerUser }} -P ${{ steps.create-container.outputs.containerPassword }}" -D"sqlcmd.path"="${{ steps.create-vars.outputs.sqlcmdPath }}" -D"sqlpackage.path"="$env:SQLPACKAGE_HOME" -D"logtable.name"="tempdb.dbo.[Full Build Log]" + + - name: create junit artifact + shell: pwsh + run: | + $ArtifactPath = "$env:TEST_ARTIFACT_DIR\"; + (Get-ChildItem $basePath -recurse -include "TestResults*.xml").FullName; + + - name: archive junit test artifact + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: jUnitTests-${{ matrix.data-image[ env.MATRIX_NAME ] }} + path: "${{ env.test_artifact_dir }}\\" + retention-days: 90 + + +# # πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– +# drop-data-container: +# name: drop the data container +# needs: test-tSQLt +# if: ${{ always() }} +# runs-on: ubuntu-latest +# env: +# steps: +# - name: decode az sp cred +# id: cred-decode +# shell: pwsh +# run: | +# Write-Host ('#TODO'); +# # documentation: https://github.com/azure/login#configure-azure-credentials diff --git a/.gitignore b/.gitignore index 4abf462f0..57a9983e2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,23 @@ /tSQLtCLR/tSQLtCLR.suo /tSQLtCLR/UnsignedEmpty/obj /tSQLtCLR/UnsignedEmpty/bin -/tSQLtCLR/tSQLtExternalAccessKey/bin -/tSQLtCLR/tSQLtExternalAccessKey/obj +/tSQLtCLR/tSQLtAssemblyKey/bin +/tSQLtCLR/tSQLtAssemblyKey/obj /tSQLtCLR/tSQLtCLR.v12.suo +*.dtbcache +*.suo +/tSQLtCLR/builderr.txt +/tSQLtCLR/buildlog.txt +/tSQLtCLR/.vs/tSQLtCLR/v15/Server/sqlite3/db.lock +/tSQLtCLR/.vs/tSQLtCLR/v15/Server/sqlite3/storage.ide +/tSQLtCLR/.vs/tSQLtCLR/v15/Server/sqlite3/storage.ide-shm +/tSQLtCLR/.vs/tSQLtCLR/v15/Server/sqlite3/storage.ide-wal +/tSQLtCLR/.vs/tSQLtCLR/v16/Server/sqlite3 +/Build_Artifact +/tSQLtCLR/OfficialSigningKey/tSQLtOfficialSigningKey.b64.txt +.DS_Store +/Build_Output +/tSQLtCLR/OfficialSigningKey/KeyInfo.txt +/tSQLtCLR/OfficialSigningKey/KeyInfoC.txt + +.vscode/settings.json diff --git a/Build/Build.ssmssqlproj b/Build/Build.ssmssqlproj deleted file mode 100644 index 1b3f50895..000000000 --- a/Build/Build.ssmssqlproj +++ /dev/null @@ -1,239 +0,0 @@ - - - - - - - 2014-12-31T18:06:01.9272147-05:00 - SQL - Dev_2014 - - Windows Authentication - - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - 2014-11-24T18:10:21.4587287-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ChangeDbAndExecuteStatement(tSQLt.Build).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - CreateBuildDb.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Drop(master.tSQLt_testutil).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Drop(tSQLtExternalAccessKey).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - EnableCLR.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - EnableExternalAccess.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ExecuteAs(tSQLt.Build.SA).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ExecuteAs(tSQLt.Build).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - GetFailedTestCount.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - GetTestResults.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - CreateLogin(tSQLt.Build).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - GrantBuildPermissions.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Install(master.tSQLt_testutil).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Install(tSQLtExternalAccessKey).sql - - - - - - - - - - BuildHelper.exe - - - - - - ChangeSigningKey_ReadMe.txt - - - - - - CreateAssemblyGenerator.exe - - - - - - License.txt - - - - - - LocalBuild.bat - - - - - - localNBuild.bat - - - - - - localNDeploy.bat - - - - - - NewRelease.doc - - - - - - ReleaseNotes.txt - - - - - - Setup.bat - - - - - - SourceForgePrivateKey.ppk - - - - - - SqlLoginNbuild.bat - - - - - - tSQLt.local_build_output.build - - - - - - VersionInfo.vbs - - - - - - build.xml - - - - - - buildNotes.txt - - - - - - buildtranslate.txt - - - - - - tSQLt.build - - - - - - tSQLt.validatebuild - - - - - \ No newline at end of file diff --git a/Build/BuildHelper.exe b/Build/BuildHelper.exe deleted file mode 100644 index 59ef93239..000000000 Binary files a/Build/BuildHelper.exe and /dev/null differ diff --git a/Build/CommonFunctionsAndMethods.psm1 b/Build/CommonFunctionsAndMethods.psm1 new file mode 100644 index 000000000..63f51d5ee --- /dev/null +++ b/Build/CommonFunctionsAndMethods.psm1 @@ -0,0 +1,286 @@ +$__=$__ #quiesce warnings +$CommonFunctionsAndMethodsDir = $PSScriptRoot +Write-Verbose "Loading CommonFunctionsAndMethods.psm1 from: $PSCommandPath" +. (Join-Path $CommonFunctionsAndMethodsDir 'SQLServerConnection.ps1'); + +$MergeHashTables = {param([HashTable]$base,[HashTable]$new);$new.GetEnumerator()|%{$base.remove($_.Key);$base += @{$_.Key=$_.Value}};$base;}; +$AddTagsToResourceGroup = +{ + param([String]$ResourceGroupName,[HashTable]$newTags); + $RG = Get-AzResourceGroup -name $ResourceGroupName; + $RG|Set-AzResourceGroup -Tags ($MergeHashTables.Invoke($RG.Tags,$newTags)[0]); +} +$GetUTCTimeStamp = {param();(Get-Date).ToUniversalTime().ToString('[yyyy-MM-ddTHH:mm:ss.fffffff UTC]');}; +$SQLPrintCurrentTime = "EXEC('DECLARE @C VARCHAR(MAX)=CONVERT(VARCHAR(MAX),SYSUTCDATETIME(),127);RAISERROR(@C,0,1)WITH NOWAIT;');"; + +Function Log-Output{[cmdletbinding()]Param([parameter(ValueFromPipeline)]$I);Process{Write-Host ([string]::Concat($GetUTCTimeStamp.Invoke()[0],[string]::Concat(" $I")));};}; + +Function Invoke-SqlFile +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][SqlServerConnection] $SqlServerConnection, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string[]] $FileNames, + [Parameter(Mandatory=$false)][string] $DatabaseName = "tempdb", + [Parameter(Mandatory=$false)][hashtable] $AdditionalParameters = @{}, + [Parameter(Mandatory=$false)][string] $ApplicationNameSuffix = $null, + [Parameter(Mandatory=$false)][bool] $PrintSqlOutput = $false + ); + $tmpInputFile = New-TemporaryFile; + $SeparatorContent = @("","GO","") + + & "$CommonFunctionsAndMethodsDir/tSQLt_Build/ConcatenateFiles.ps1" -OutputFile $tmpInputFile -InputPath $FileNames -SeparatorContent $SeparatorContent + + $parameters = @{ + ConnectionString = ($SqlServerConnection.GetConnectionString($DatabaseName,$ApplicationNameSuffix)) + InputFile = $tmpInputFile + Variable = $AdditionalParameters + } + if($PrintSqlOutput){ + $parameters['Verbose'] = $true + } + + $dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(CommonFunctionsAndMethods.p1:Invoke-SqlFile:Invoke-SqlCommand[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $results = (Invoke-SqlCmd @parameters) + $dddafter = Get-Date;Write-Verbose("------->>After<<-------(CommonFunctionsAndMethods.p1:Invoke-SqlFile:Invoke-SqlCommand[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + Write-Verbose("Runtime in Milliseconds: $(($dddafter-$dddbefore).TotalMilliseconds)") + return $results +} + +Function Invoke-SQLFileOrQuery +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][SqlServerConnection] $SqlServerConnection, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $HelperSQLPath, + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $DatabaseName = 'tempdb', + [Parameter(Mandatory=$true, ParameterSetName="Elevated")] + [switch]$Elevated, + [Parameter(Mandatory=$true, ParameterSetName="Caller")] + [switch]$Caller, + [Parameter(Mandatory=$false, ParameterSetName="Basic")] + [switch]$Basic, + [Parameter(Mandatory=$false)][string] $OutputFile = $null, + [Parameter(Mandatory=$false)][string] $Query = "", + [Parameter(Mandatory=$false)][string[]] $Files = @(), + [Parameter(Mandatory=$false)][hashtable] $AdditionalParameters = @{}, + [Parameter(Mandatory=$false)][bool] $PrintSqlOutput = $false + ); + $tempFile = $null; + + # Write-Warning("------->>NOTE<<-------(tSQLt_Validate.ps1:Invoke-SQLFileOrQuery:Adding Timestamps to Query") + # $Query = 'PRINT CONVERT(VARCHAR(MAX),SYSUTCDATETIME(),127);'+$Query+'PRINT CONVERT(VARCHAR(MAX),SYSUTCDATETIME(),127);' + + try{ + @{ + BuildLogTableName=$LogTableName + DbName = "tempdb" + ExecuteStatement=";" + NewDbName = ("[This Shouldn't be here "+(New-Guid)+']') + }.GetEnumerator()|ForEach-Object{if(!$AdditionalParameters.Contains($_.key)){$AdditionalParameters[$_.key]=$_.value;}} + + [string[]]$FileNames = @(); + if($Elevated){ + $FileNames += (Join-Path $HelperSQLPath "temp_executeas_sa.sql"|Resolve-Path) + }elseif($Caller){ + $FileNames += (Join-Path $HelperSQLPath "temp_executeas_caller.sql"|Resolve-Path) + }else{ + $FileNames += (Join-Path $HelperSQLPath "temp_executeas.sql"|Resolve-Path) + } + if (![string]::IsNullOrWhiteSpace($Query)){ + $FileNames += Get-TempFileForQuery($Query) + } + $FullFileNameSet = @($FileNames)+@($Files); + $FullFileNameSet += (Join-Path $HelperSQLPath "temp_executeas_cleanup.sql"|Resolve-Path); + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + FileNames = $FullFileNameSet + DatabaseName = $DatabaseName + AdditionalParameters = $AdditionalParameters + PrintSqlOutput = $PrintSqlOutput + } +$dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-SQLFileOrQuery:Invoke-SqlFile[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $QueryOutput = Invoke-SqlFile @parameters +$dddafter = Get-Date;Write-Verbose("------->>After<<-------(tSQLt_Validate.ps1:Invoke-SQLFileOrQuery:Invoke-SqlFile[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") +Write-Verbose("Runtime in Milliseconds: $(($dddafter-$dddbefore).TotalMilliseconds)") + return $QueryOutput + } + catch{ + throw + } + finally{ + if($null -ne $tempFile){ + Remove-Item -Path $tempFile.FullName -ErrorAction Ignore + } + } +} + +function Get-TempFileForQuery { + [CmdletBinding()] + [OutputType([string])] + param ( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Query + ) + $tempFile = New-TemporaryFile; + $Query |Set-Content -Path $tempFile + return $tempFile +} + + +function Get-FriendlySQLServerVersion { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][SqlServerConnection] $SqlServerConnection, + [Parameter(Mandatory=$false)][switch]$Quiet + ) + $GetFriendlySQLServerVersionFullPath = (Get-ChildItem -Path ($PSScriptRoot + '/output/*') -include "GetFriendlySQLServerVersion.sql" -Recurse | Select-Object -First 1 ).FullName; + $resultSet = (Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames @($GetFriendlySQLServerVersionFullPath) -DatabaseName 'tempdb'); + $FriendlyVersion = ($resultSet.FriendlyVersion) + if(!$Quiet){Log-Output "Friendly SQL Server Version: $FriendlyVersion"}; + return $FriendlyVersion +} + +function Update-Archive { + param ( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $ArchiveName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String[]] $FileNamesToInclude + ) + <# Create directory to unpack compressed file #> + $ArchiveDirectory = $ArchiveName + "." + (Get-Date -Format "yyyy-MM-dd-HHmm"); + New-Item -ItemType Directory -Path ".\$ArchiveDirectory"; + + <# Unpack compressed file into newly created directory #> + Expand-Archive -LiteralPath $ArchiveName -DestinationPath $ArchiveDirectory; + + <# Rename original archive file #> + $NewArchiveName = $ArchiveName + ".original"; + Rename-Item -Path $ArchiveName -NewName $NewArchiveName; + + <# Copy new files to include into the archive directory #> + foreach ($FileName in $FileNamesToInclude) { + Copy-Item $FileName -Destination $ArchiveDirectory; + } + + <# Compress files #> + $compress = @{ + Path = $ArchiveDirectory + CompressionLevel = "Fastest" + DestinationPath = $ArchiveName + } + Compress-Archive @compress +} + +function Remove-DirectoryQuietly { + param ( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $Path + ) + if (Test-Path -Path $Path) { + Remove-Item -Path $Path -Recurse -Force + } +} + +Function Remove-ResourceGroup{ + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ResourceGroupName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $BuildId); + + Write-Verbose "β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-"; + Write-Verbose ("[{0}]Start processing delete for {1}" -f ((get-date).toString("O")), ($ResourceGroupName)); + + $MyAzResourceGroup = (Get-AzResourceGroup -name "$ResourceGroupName"); + if(("RemovalBy" -in $MyAzResourceGroup.tags.keys) -and (![string]::isnullorempty($MyAzResourceGroup.tags.RemovalBy))) { + $MyAzResourceGroup = $null; + } + if($null -ne $MyAzResourceGroup) { + $Tags = @{}; + Write-Verbose ("Add Tag to {0}" -f $ResourceGroupName); + $Tags = $MyAzResourceGroup.Tags; + $Tags.remove("RemovalBy"); + $Tags += @{"RemovalBy"="$BuildId"}; + $MyAzResourceGroup | Set-AzResourceGroup -Tags $Tags; + Start-Sleep 10; + Write-Verbose ("Confirming Tags are still in place for {0}" -f $ResourceGroupName); + $MyAzResourceGroup = $MyAzResourceGroup | Get-AZResourceGroup | Where-Object {$_.Tags.RemovalBy -eq "$BuildId"}; + $MyAzResourceGroup.Tags | Format-Table; + + if($null -ne $MyAzResourceGroup) { + Write-Verbose "Removing Locks" + $retrievedResourceGroupName = $MyAzResourceGroup.ResourceGroupName; + Get-AzResource -ResourceGroupName $retrievedResourceGroupName | ForEach-Object { + Get-AzResourceLock -ResourceType $_.ResourceType -ResourceName $_.Name -ResourceGroupName $_.ResourceGroupName | ForEach-Object{ + Write-Verbose ("{0} -> {1}" -f $_.ResourceType, $_.ResourceName); + $_ | Remove-AzResourceLock -Force + } + } + Write-Verbose ("Removing RG {0}" -f $retrievedResourceGroupName); + $MyAzResourceGroup | Remove-AzResourceGroup -Force; + } + else { + Write-Verbose ("Tags changed by another process. Resource Group {0} is no longer eligible to be deleted." -f $ResourceGroupName); + } + } + else { + Write-Verbose ("Processing skipped for Resource Group: {0} Build Id: {1}" -f $ResourceGroupName, $BuildId); + } + Write-Verbose ("[{0}]Done processing delete for {1}" -f ((get-date).toString("O")), ($ResourceGroupName)) + Write-Verbose "β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-β–€-β–„-_-β–„-"; +} + +Function Get-SnipContent { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true,ValueFromPipeline=$true)][AllowEmptyString()][string[]]$searchArray, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $startSnipPattern, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $endSnipPattern + ) + begin { + $outputOn = $false; + }; + process { + $searchArray | ForEach-Object { + if($_ -eq $endSnipPattern) { $outputOn = $false }; + if($outputOn) { $_ }; + if($_ -eq $startSnipPattern) { $outputOn = $true }; + } + }; + end { + }; +} + +Function Replace-InFile { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true,ValueFromPipeline=$true)][AllowEmptyString()][string[]]$filePath, + [Parameter(Mandatory=$true)][array] $replacements + ) + begin { + }; + process { + $fileContent = (Get-Content -Path $filePath) + $replacements|ForEach-Object{ + $sv = $_[0] + $rv=$_[1]; + $isRegex = $false; + if($rv -is [array]){ + $isRegex = $true + $rv = $rv[0] + } + Write-Verbose("Replacing >$_< with >$rv<..."); + if($isRegex){ + $fileContent = $fileContent -replace $_, $rv + }else{ + $fileContent = $fileContent.Replace($_, $rv) + } + } + $fileContent | Set-Content -Path $releaseNotesPath + }; + end { + }; +} + +Log-Output($GetUTCTimeStamp.Invoke(),"Done: Loading CommonFunctionsAndMethods"); +Export-ModuleMember -Function Log-Output, Invoke-SqlFile, Get-TempFileForQuery, Get-FriendlySQLServerVersion, Update-Archive +Export-ModuleMember -Function Remove-ResourceGroup, Get-SnipContent, Replace-InFile, Invoke-SQLFileOrQuery, Remove-DirectoryQuietly +Export-ModuleMember -Variable $CommonFunctionsAndMethodsDir, $SQLPrintCurrentTime diff --git a/Build/CreateAssemblyGenerator.exe b/Build/CreateAssemblyGenerator.exe deleted file mode 100644 index 212a5dfb5..000000000 Binary files a/Build/CreateAssemblyGenerator.exe and /dev/null differ diff --git a/Build/CreateBuildDb.sql b/Build/CreateBuildDb.sql deleted file mode 100644 index 017ad6b86..000000000 --- a/Build/CreateBuildDb.sql +++ /dev/null @@ -1,15 +0,0 @@ -GO -IF(DB_ID('$(NewDbName)') IS NOT NULL) -BEGIN - EXEC(' - ALTER DATABASE $(NewDbName) SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE; - USE $(NewDbName); - ALTER DATABASE $(NewDbName) SET SINGLE_USER WITH ROLLBACK IMMEDIATE; - USE tempdb; - DROP DATABASE $(NewDbName); - '); -END -GO -CREATE DATABASE $(NewDbName) COLLATE SQL_Latin1_General_CP1_CS_AS WITH TRUSTWORTHY OFF; --- COLLATE SQL_Latin1_General_CP1_CS_AS -GO diff --git a/Build/CreateLogin(tSQLt.Build).sql b/Build/CreateLogin(tSQLt.Build).sql deleted file mode 100644 index 6c9dfddab..000000000 --- a/Build/CreateLogin(tSQLt.Build).sql +++ /dev/null @@ -1,17 +0,0 @@ -GO -IF SUSER_SID('tSQLt.Build') IS NULL - CREATE LOGIN [tSQLt.Build] WITH PASSWORD = 0x010095EBA5D9A28749DF6ABFD4F5AAFBCE8BD839E0E35D6273B0 HASHED, CHECK_POLICY = OFF, DEFAULT_DATABASE = tempdb; -GO -IF SUSER_SID('tSQLt.Build.SA') IS NULL - CREATE LOGIN [tSQLt.Build.SA] WITH PASSWORD = 0x010095EBA5D9A28749DF6ABFD4F5AAFBCE8BD839E0E35D6273B2 HASHED, CHECK_POLICY = OFF, DEFAULT_DATABASE = tempdb; -GO -EXEC sys.sp_addsrvrolemember @loginame = N'tSQLt.Build.SA', @rolename = N'sysadmin' -EXEC sys.sp_addsrvrolemember @loginame = N'tSQLt.Build', @rolename = N'dbcreator' -GO ---EXEC master.sys.sp_executesql N'GRANT EXTERNAL ACCESS ASSEMBLY TO [tSQLt.Build];'; -GO -EXEC master.sys.sp_executesql N'IF USER_ID(''tSQLt.Build'') IS NOT NULL DROP USER [tSQLt.Build];'; -EXEC master.sys.sp_executesql N'CREATE USER [tSQLt.Build] FROM LOGIN [tSQLt.Build];'; -GO ---SELECT SUSER_NAME(SP.grantee_principal_id),* FROM sys.server_permissions AS SP WHERE SP.grantor_principal_id = SUSER_ID('tSQLt.Build') ---EXEC master.sys.sp_executesql N'REVOKE IMPERSONATE ON LOGIN::[tSQLt.Build] FROM [MacWin7A\TeamCity];'; \ No newline at end of file diff --git a/Build/Drop(tSQLtExternalAccessKey).sql b/Build/Drop(tSQLtExternalAccessKey).sql deleted file mode 100644 index 60f326987..000000000 --- a/Build/Drop(tSQLtExternalAccessKey).sql +++ /dev/null @@ -1,5 +0,0 @@ - IF SUSER_ID('tSQLtExternalAccessKey') IS NOT NULL DROP LOGIN tSQLtExternalAccessKey; - - DECLARE @cmd NVARCHAR(MAX); - SET @cmd = N'IF ASYMKEY_ID(''tSQLtExternalAccessKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtExternalAccessKey;'; - EXEC master.sys.sp_executesql @cmd; diff --git a/Build/EnableCLR.sql b/Build/EnableCLR.sql deleted file mode 100644 index bd589a229..000000000 --- a/Build/EnableCLR.sql +++ /dev/null @@ -1,22 +0,0 @@ -/* - Copyright 2011 tSQLt - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -EXEC sp_configure 'clr enabled', 1; -RECONFIGURE; -GO ---DECLARE @cmd NVARCHAR(MAX); ---SET @cmd='ALTER DATABASE ' + QUOTENAME(DB_NAME()) + ' SET TRUSTWORTHY ON;'; ---EXEC(@cmd); -GO diff --git a/Build/EnableExternalAccess.sql b/Build/EnableExternalAccess.sql deleted file mode 100644 index dc9bf5fd8..000000000 --- a/Build/EnableExternalAccess.sql +++ /dev/null @@ -1 +0,0 @@ -EXEC tSQLt.EnableExternalAccess; diff --git a/Build/GetTestResults.sql b/Build/GetTestResults.sql deleted file mode 100644 index 0d7ed8aa8..000000000 --- a/Build/GetTestResults.sql +++ /dev/null @@ -1,2 +0,0 @@ -:XML ON -EXEC [tSQLt].[XmlResultFormatter]; diff --git a/Build/Install(tSQLtExternalAccessKey).sql b/Build/Install(tSQLtExternalAccessKey).sql deleted file mode 100644 index 5f806af63..000000000 --- a/Build/Install(tSQLtExternalAccessKey).sql +++ /dev/null @@ -1 +0,0 @@ -EXEC tSQLt.InstallExternalAccessKey; diff --git a/Build/LocalBuild.bat b/Build/LocalBuild.bat deleted file mode 100644 index 722c6bdc3..000000000 --- a/Build/LocalBuild.bat +++ /dev/null @@ -1 +0,0 @@ -cmd /C %1\bin\ant -Dmsbuild.path=%2 -Ddb.server=%3 -Ddb.name=%4 %5 diff --git a/Build/LocalBuild.ps1 b/Build/LocalBuild.ps1 new file mode 100644 index 000000000..e8bd67647 --- /dev/null +++ b/Build/LocalBuild.ps1 @@ -0,0 +1,110 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $ServerName = 'localhost,1433', + [Parameter(Mandatory=$true, ParameterSetName = 'UserPass')][ValidateNotNullOrEmpty()][string] $UserName = "sa" , + [Parameter(Mandatory=$true, ParameterSetName = 'UserPass')][ValidateNotNullOrEmpty()][securestring] $Password = (ConvertTo-SecureString "P@ssw0rd" -AsPlainText), + [Parameter(Mandatory=$true, ParameterSetName = 'TrustedCon')][ValidateNotNullOrEmpty()][switch] $TrustedConnection, + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $DatabaseName = 'tSQLt.TmpBuild.DacPacBuild', + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $pfxFilePath = (Join-Path $env:TSQLTCERTPATH "tSQLtOfficialSigningKey.pfx" |Resolve-Path), + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][securestring] $pfxPassword = (ConvertTo-SecureString -String "$env:TSQLTCERTPASSWORD" -Force -AsPlainText), + [Parameter(Mandatory=$false)][switch]$KeepTemp, + [Parameter(Mandatory=$false, ParameterSetName="IgnoreMe")][string]$IgnoreMe +) +$PSDefaultParameterValues = $PSDefaultParameterValues.clone() +$PSDefaultParameterValues += @{'*:ErrorAction' = 'Stop'} + +Write-Verbose "Starting execution of LocalBuild.ps1" +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +$cfam = (Join-Path $invocationDir "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Verbose "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force -Verbose +. (Join-Path $invocationDir 'SQLServerConnection.ps1'); + +function CleanTemp { + param ( + [Parameter(Mandatory=$false)][bool]$KeepTemp = $false + ) + if(! $KeepTemp){ + if (Test-Path -Path "temp") { + Remove-Item -Path "temp" -Recurse -Force + } + } +} + +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +try{ + + Log-Output(''); + Log-Output("+--------------------------------------------------------------------+"); + Log-Output("| ***** Executing Local tSQLt Build ***** |"); + Log-Output("+--------------------------------------------------------------------+"); + Log-Output(''); + + if($TrustedConnection){ + Log-Output('Selecting Trusted Connection') + $SqlServerConnection = [SqlServerConnection]::new($ServerName,"LocalBuild"); + }else{ + Log-Output('Selecting UserName/Password Connection') + $SqlServerConnection = [SqlServerConnection]::new($ServerName,$UserName,$Password,"LocalBuild"); + } + + + Log-Output('+ - - - - - - - - - - - - - - - - - +') + Log-Output(': Cleaning Environment :') + Log-Output('+ - - - - - - - - - - - - - - - - - +') + + if (Test-Path -Path "output") { + Remove-Item -Path "output" -Recurse -Force + } + CleanTemp; + + Log-Output('+ - - - - - - - - - - - - - - - - - +') + Log-Output(': Starting CLR Build :') + Log-Output('+ - - - - - - - - - - - - - - - - - +') + + & ./tSQLt_BuildCLR.ps1 -pfxFilePath $pfxFilePath -pfxPassword $pfxPassword + CleanTemp $KeepTemp; + + Log-Output('+ - - - - - - - - - - - - - - - - - +') + Log-Output(': Starting tSQLt Build :') + Log-Output('+ - - - - - - - - - - - - - - - - - +') + + & ./tSQLt_Build.ps1 + CleanTemp $KeepTemp; + + Log-Output('+ - - - - - - - - - - - - - - - - - +') + Log-Output(': Starting tSQLt Tests Build :') + Log-Output('+ - - - - - - - - - - - - - - - - - +') + + & ./tSQLt_BuildTests.ps1 + CleanTemp $KeepTemp; + + Log-Output('+ - - - - - - - - - - - - - - - - - +') + Log-Output(': Starting tSQLt DacPac Build :') + Log-Output('+ - - - - - - - - - - - - - - - - - +') +# Write-Warning($SqlServerConnection) + & ./tSQLt_BuildDacpac.ps1 -SqlServerConnection $SqlServerConnection -DacPacDatabaseName $DatabaseName + CleanTemp $KeepTemp; + + Log-Output('+ - - - - - - - - - - - - - - - - - +') + Log-Output(': Packaging tSQLt & DACPACs :') + Log-Output('+ - - - - - - - - - - - - - - - - - +') + + & ./tSQLt_BuildPackage.ps1 + CleanTemp $KeepTemp; + + Log-Output('+ - - - - - - - - - - - - - - - - - +') + Log-Output(': Build Finished :') + Log-Output('+ - - - - - - - - - - - - - - - - - +') + + +} +catch{ + throw; +} +finally{ + Pop-Location +} \ No newline at end of file diff --git a/Build/LocalValidate.ps1 b/Build/LocalValidate.ps1 new file mode 100644 index 000000000..dd14882a0 --- /dev/null +++ b/Build/LocalValidate.ps1 @@ -0,0 +1,64 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $ServerName = 'localhost,1433', + [Parameter(Mandatory=$true, ParameterSetName = 'UserPass')][ValidateNotNullOrEmpty()][string] $UserName = "sa" , + [Parameter(Mandatory=$true, ParameterSetName = 'UserPass')][ValidateNotNullOrEmpty()][securestring] $Password = (ConvertTo-SecureString "P@ssw0rd" -AsPlainText), + [Parameter(Mandatory=$true, ParameterSetName = 'TrustedCon')][ValidateNotNullOrEmpty()][switch] $TrustedConnection, + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $DatabaseName = 'tSQLtValidateBuild', + [Parameter(Mandatory=$false, ParameterSetName="IgnoreMe")][string]$IgnoreMe +) +$PSDefaultParameterValues = $PSDefaultParameterValues.clone() +$PSDefaultParameterValues += @{'*:ErrorAction' = 'Stop'} + +Write-Verbose "Starting execution of LocalBuild.ps1" +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +$cfam = (Join-Path $invocationDir "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Verbose "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force -Verbose +. (Join-Path $invocationDir 'SQLServerConnection.ps1'); + + +try{ + + if($TrustedConnection){ + Log-Output('Selecting Trusted Connection') + $SqlServerConnection = [SqlServerConnection]::new($ServerName,"LocalBuild"); + }else{ + Log-Output('Selecting UserName/Password Connection') + $SqlServerConnection = [SqlServerConnection]::new($ServerName,$UserName,$Password,"LocalBuild"); + } + + Log-Output(''); + Log-Output("+--------------------------------------------------------------------+"); + Log-Output("| ***** Executing Local tSQLt Build Validation ***** |"); + Log-Output("+--------------------------------------------------------------------+"); + Log-Output(''); + + + Log-Output('+ - - - - - - - - - - - - - - - - - +') + Log-Output(': Starting Build Validation :') + Log-Output('+ - - - - - - - - - - - - - - - - - +') + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + MainTestDb = 'tSQLt.TmpBuild.ValidateBuild' + DacpacTestDb = 'tSQLt.TmpBuild.ValidateDacPac' + ExampleTestDb = 'tSQLt.TmpBuild.ValidateExample' + } + + & ./tSQLt_Validate.ps1 @parameters + + Log-Output('+ - - - - - - - - - - - - - - - - - +') + Log-Output(': Build Validation Finished :') + Log-Output('+ - - - - - - - - - - - - - - - - - +') + + +} +catch{ + throw; +} +finally{ + Pop-Location +} \ No newline at end of file diff --git a/Build/Readme.md b/Build/Readme.md new file mode 100644 index 000000000..d130bf7fd --- /dev/null +++ b/Build/Readme.md @@ -0,0 +1,13 @@ +# Building tSQLt + +To build tSQLt on your local machine, clode the repository in a local folder: + +`git clone https://` + +Open that folder in Visual Studio Code, aka VSC (Open Source, download from [https://code.visualstudio.com/download](https://code.visualstudio.com/download).) + +You also need Docker Dektop (Free for non-commercial use, Download from [https://www.docker.com/products/docker-desktop/](https://www.docker.com/products/docker-desktop/).) This might work with other container platforms, too. + +Once ready, type `>Dev Containers: Open Folder in Container...` into the top bar in VSC and hit Enter. + +This starts up your development container, that includes all the software needed to build tSQLt. \ No newline at end of file diff --git a/Build/ReleaseNotes.txt b/Build/ReleaseNotes.txt index 7ed55ab54..0d8b132b1 100644 --- a/Build/ReleaseNotes.txt +++ b/Build/ReleaseNotes.txt @@ -1,415 +1,552 @@ -tSQLt Release Notes -=================== -An online version of these release notes is available at: -http://tsqlt.org/category/release-notes/ - - ---------------------------- -Release: VLATEST-BUILD-NUMBER - -BUG FIXES: - -1. Fixed error message in tSQLt.FakeTable - -2. tSQLt.DropClass now handles already quoted names correctly - -NEW FEATURES: - -1. The tSQLt CLR is now signed with a new key: - Public Key Token = 0x7722217d36028e4c - Public Key: 0x0602000000240000525341310004000001000100F7D9A45F2B508C2887A8794B053CE5DEB28743B7C748FF545F1F51218B684454B785054629C1417D1D3542B095D80BA171294948FCF978A502AA03240C024746B563BC29B4D8DCD6956593C0C425446021D699EF6FB4DC2155DE7E393150AD6617EDC01216EA93FCE5F8F7BE9FF605AD2B8344E8CC01BEDB924ED06FD368D1D0 - The password required to sign an assembly with this key is no longer part of the code - base. This was necessary to be able to provide a secure method of installing tSQLt not - requiring the database to be set to TRUSTWORTHY anymore. - -2. tSQLt.Info() now returns the public key token of the assembly's signing key in - the CLRSigningKey column - -3. tSQLt.RunNew executes all tests in test classes (schemata) that were created with - tSQLt.NewTestClass after the last call to tSQLt.Reset - -4. tSQLt.InstallExternalAccessKey installs the required objects in the master database - to allow tSQLt to execute with EXTERNAL_ACCESS without the database being TRUSTWORTHY - -5. tSQLt.RemoveExternalAccessKey removes those objects from the master database - -6. tSQLt.EnableExternalAccess can be used to manually enable and disable EXTERNAL_ACCESS - -7. tSQLt automatically tries to enable EXTERNAL_ACCESS, each time any "run" method is called. - Enabling EXTERNAL_ACCESS is possible when either the database is TRUSTWORTHY and owned - by a server principal with EXTERNAL_ACCESS_ASSEMBLY permission, or if - tSQLt.InstallExternalAccessKey has been executed before on the server. - - This feature establishes backward compatibillity but comes at a performance cost. - It might therefore be removed in a future version. - -8. tSQLt detects at the beginning of each execution, if the requirements to enable - EXTERNAL_ACCESS are not any longer fulfilled. If the assembly is still marked as - EXTERNAL_ACCESS, the execution is halted immediately, and an error is reported - back to the caller. - -9. tSQLt checks at the beginning of each execution, if the installed assembly's version - matches the version of the T-SQL code. If a mismatch is detected, the execution is - immediately halted and an error is reported back to the caller. - -A. tSQLt.Run now allows a test result formatter to be passed in in the @TestResultFormatter - parameter. If that parameter is omitted or NULL, the default result formatter is used. - -OTHER: - -1. Cleaned up several procedures and tests - -2. Removed extraneous DROP statements from tSQLt install file. - ---------------------------- -Release: V1.0.5793.20044 - -BUG FIXES: -1. AssertEqualsTableSchema now handles gaps in column_id values correctly (thanks Greg L.) -2. Fixed handling of empty messages in AssertEmptyTable - -NEW FEATURES: -1. AssertStringIn asserts that a string value is element of a set of string values -2. ApplyConstraint can now apply cascading actions on foreign keys -3. FakeTable can now fake synonyms of tables and view (in the same database only, for now) - -OTHER: - ---------------------------- -Release: V1.0.5686.18945 - -BUG FIXES: -1. The XML output now validates against the JUnit test result XML schema at - https://raw.githubusercontent.com/windyroad/JUnit-Schema/master/JUnit.xsd - While there is no "official" JUnit schema, the above is the one that is - referenced most often online. - -2. tSQLt.DropClass now handles XML schemata correctly. - -3. All tSQLt assertions can now be called with a @Message parameter. In case of a - failure, the value of that parameter will be output before the default failure - message. - For backward compatibility, tSQLt.AssertEqualsTable still has a @FailMsg - parameter. Its use is now deprecated. - -NEW FEATURES: -1. tSQLt.Info() now returns the version and build of the SQL Server Instance it is installed on: - - SELECT * FROM tSQLt.Info() AS I; - - Version ClrVersion SqlVersion SqlBuild - -------------- -------------- ---------- -------- - 1.0.5479.30419 1.0.5479.30419 12.00 4213.00 - -2. Verbose execution mode - Executing EXEC tSQLt.SetVerbose @Verbose = 1; before running the tests will cause - tSQLt to output the test name at the beginning and the end of each test's execution. - That makes it easier in large test suites to find the output of a particular test. - -3. tSQLt.RunC - The new procedure tSQLt.RunC behaves identical to tSQLt.Run. However, instead of - expecting the test (class) name in a parameter, it parses the INPUTBUFFER and - extracts the name from a specially formed comment: - - EXEC tSQLt.RunC;--Run_Methods_Tests.[test tSQLt.RunC calls tSQLt.Run with everything after ;-- as @TestName] - - This makes for a more powerful SQL Query shortcut in SSMS as test names now do not have to be quoted anymore. - -4. tSQLt.AssertEqualsTableSchema - tSQLt.AssertEqualsTableSchema is called like tSQLt.AssertEqualsTable. Instead of - the table contents, it compares the columns including name, datatype, collation, - NULL-ability and identity property. - -5. The test runner now captures start and end time for each test. This information - is included in the XML output. The default output contains the execution duration - for each test. - -6. Both tSQLt.SpyProcedure and tSQLt.FakeFunction now handle table type parameters. - The content of a table type parameter in a spied procedure is converted into - XML and included in the _SpyProcedureLog table. - -OTHER: - -1. Farewell Sourceforge - Because of several issues over the last months and because of even more complaints - about Sourceforge's business practices by tSQLt users, we decided to find a more appropriate space. - The official downloads are now available directly on tSQLt.org. The source code - repository will find its new home either on github or bitbucket over the next few days. - -2. The fail message of tSQLt.AssertEqualsString is now broken into two lines - with aligned string values for easier comparison. - -3. The installation script now prints a welcome message. - - - ---------------------------- -Release: V1.0.5325.27056 - -BUG FIXES: -1. FakeTable now handles CHAR UDTs - -NEW FEATURES: -1. SQL 2014 now officially supported -2. tSQLt.ApplyConstraint now handles UNIQUE and PRIMARY KEY constraints -3. tSQLt.DropClass now handles UDTs -4. tSQLt.ExpectNoException can be followed by tSQLt.ExpectException -5. Added @IfExists parameter to tSQLt.RemoveObject -6. Added tSQLt.RemoveObjectIfExists -7. Added tSQLt.AssertObjectDoesNotExist - ---------------------------- -Release: V1.0.5137.39257 - -NEW FEATURES: -1. added tSQLt.RenameClass - - ---------------------------- - -Release: V1.0.5071.16906 - -NEW FEATURES: -1. added tSQLt.FakeFunction -2. added @ExpectedErrorNumber parameter to tSQLt.ExpectException - - ---------------------------- - -Release: V1.0.4969.33062 - -NEW FEATURES: -1. tSQLt.ApplyTrigger -2. tSQLt.ExpectNoException -OTHER: -1. The output of a test that errors out now contains the severity and state of that error. - ---------------------------- - -Release: V1.0.4941.23369 - -BUG FIXES: -1. tSQLt.Fail now works when transaction is in an uncommittable state. -NEW FEATURES: -1. tSQLt.AssertNotEquals -2. tSQLt.AssertEmptyTable -3. tSQLt.ExpectException -OTHER: -1. We have now a set of snippets for Red Gate SQL Prompt to support test development - Instructions of how to get them are available on our downloads page: http://tSQLt.org/downloads - ---------------------------- - -Release: V1.0.4822.19862 - -BUG FIXES: -1. Corrected defect in AssertEqualsTable where the custom error message was not being displayed. -NEW FEATURES: -1. Added tSQLt.RemoveObject procedure. - ---------------------------- - -Release: V1.0.4735.30771 - -BUG FIXES: -1. AssertEqualsTable did not quote columns. -NEW FEATURES: -1. Added AssertLike procedure. -2. Added support for multi-column foreign keys in ApplyConstraint. - ---------------------------- - -Release: V1.0.4721.29450 - -BUG FIXES: -1. When executing tSQLt.Run on an individual test, the setup procedure was sometimes not - called based on the case of the setup procedure name. -OTHER: -1. Added StubRecord procedure back in, however it is marked as 'not supported'. - ---------------------------- - -Release: V1.0.4643.26915 - -OTHER: -1. Removed unused procedure: StubRecord -2. Improved the performance of AssertEqualsTable -3. AssertEqualsTable now gives reasonalbe error messages for unsupported datatypes. More - information on unsupported datatypes is available on the documentation page for - AssertEqualsTable: http://tsqlt.org/user-guide/assertions/assertequalstable/ - ---------------------------- -Release: V1.0.4504.21220 - -BUG FIXES: -1. tSQLt.NewTestClass now does not drop schema objects if the schema is not a test class - ---------------------------- -Release: V1.0.4496.29340 - -BUG FIXES: -1. tSQLt.ResultSetFilter now supports data types introduced in SQL Server 2008 and CLR - datatypes. -2. tSLQt.AssertResultSetsHaveSameMetaData now ignores "hidden" columns. For example, - when comparing the metadata of a view, this procedure had been including underlying - columns. - ---------------------------- -Release: V1.0.4462.23207 - -NEW FEATURES: -1. tSQLt.FakeTable now supports @Defaults parameter. If @Defaults = 1 default - constraints will be preserved on columns. -2. tSQLt.FakeTable now supports @ComputedColumns parameter. If - @ComputedColumns = 1 computed columns will be preserved. - ---------------------------- - -Release: 1.0.4413.31717 - -NEW FEATURES: -1. tSQLt.Info() now reports Version and CLRVersion. These should always match! -2. tSQLt.FakeTable now supports @Identity parameter. If @Identity = 1 the - identity property of the table is preserved. - ---------------------------- - -Release: V1.0.4357.27914 - -BUG FIXES: -1. tSQLt.SpyProcedure can handle user defined types. User defined types may be in schemas and - may also be not nullable. - -NEW FEATURES: -1. SetUp can be named in any combination of upper or lower case characters (e.g. setup, SETUP, - SeTuP, etc). ---------------------------- - -Release: V1.0.4351.28410 - -BUG FIXES: -1. tSQLt.NewTestClass now handles schema names with spaces and other special characters -2. tSQLt.NewTestClass now handles if a quoted name is passed for the new schema name - -NEW FEATURES: -1. tSQLt.Uninstall removes tSQLt from the database -2. tSQLt.RunWithXmlResults executes like tSQLt.Run, but produces results is XML -3. tSQLt.TestClasses is a view that lists properties of test classes -4. tSQLt.Tests is a view that lists properties of test cases -5. Example.sql includes an example database and is referenced by the updated Quick Start - (http://tsqlt.org/quick-start) -6. tSQLt.Info() provides information about the installed version of tSQLt. - -OTHER: -1. New numbering system for the tSQLt builds. The current version is: V1.0.4351.28410 ---------------------------- - -build.12: - -BUG FIXES: -1. tSQLt.Fail now handles NULL values as parameters. -2. Corrected XmlResultFormatter to have root element of 'testsuites' instead of 'root'. -This improves compatibility with more continuous integration servers. - -NEW FEATURES: -1. FakeTable now handles a single parameter, combining the schema and table name. -This makes FakeTable more consistent with other methods in tSQLt. -2. ApplyConstraint now handles two parameters, combining the schema and table -name as the first parameter. This makes ApplyConstraint more consistent with other -methods in tSQLt. - ---------------------------- - -build.11: - -BUG FIXES: -1. ApplyConstraint for a Foreign Key that references a Faked Table is fixed. -2. SetClrEnabled.sql utlity file now handles database names with spaces. -3. Test cases whose names contain a percent sign are now displayed correctly -in the test case output. - -NEW FEATURES: -1. Warnings about renaming objects displayed when executing FakeTable or -ApplyConstraint are now hidden. -2. New method: tSQLt.SuppressOutput suppresses the console output that would -be displayed by executing a command. -3. New method: tSQLt.CaptureOutput logs the console output from executing a -command into the tSQLt.CaptureOutputLog table. - ---------------------------- - -build.10: - -BUG FIXES: -1. Standardized capitalization of table, column, procedure and function names. - -NEW FEATURES: -1. Added support for case sensitive databases. - ---------------------------- - -build.9: - -BUG FIXES: -1. When using tSQLt.Run or tSQLt.RunTestClass, if an object name on the dbo -schema had the same name as a test class, the test class could not be executed. - -2. If the output of an AssertTableEquals or the length of a test case name was -too long, an error was be produced. - -NEW FEATURES: -1. A new procedure, tSQLt.NewConnection, was added which allows statements to -be executed synchronously in a different connection context. - ---------------------------- - -build.8a: - -BUG FIXES: -1. When executing tSQLt.ResultSetFilter, if the result set metadata contained -hidden columns then those columns would be returned in the result set output. -The values of those columns would be null. Hidden columns are typically present -when the base tables of a result set contain primary keys or other constraints -which were not selected. tSQLt.ResultSetFilter has been updated to not return -the hidden columns. - ---------------------------- - -build.8: - -NEW FEATURES: -1. SpyProcedure now supports output parameters. Details are available in the -User Guide. See: http://www2.sqlity.net/tsqlt/spyprocedure - -2. The first major sections of the User Guide are now available at: -http://www.tsqlt.org - On the right hand side is a link for the User Guide. - ---------------------------- - -build.7: - -IMPORTANT: tSQLt now utilizes CLR (Common Language Runtime) stored procedures. -In order to install tSQLt, CLRs must be enabled in SQL Server. The -SetClrEnabled.sql file has the command which enables CLRs. If CLRs are not -already enabled, this command must be executed before installing tSQLt. - -NEW FEATURES: -1. A new procedure tSQLt.RunAll, executes all test classes created with the -tSQLt.NewTestClass procedure. - -2. Added procedure tSQLt.AssertResultSetsHaveSameMetaData which allows the -meta data of two result sets to be compared. This compares several properties -of each column of the result set including the column name, data type, length, -precision, scale and other properties. - -3. Added procedure tSQLt.ResultSetFilter which returns a single result set from -a statement which produces multiple result sets. For example, you want to test -a stored procedure which executes several select statements. You can now use -ResultSetFilter to choose which result set to emit, and therefore you can -capture that result set into a table for use with tSQLt.AssertEqualsTable. - -4. The results of running tests can now be output in an XML format. After -calling one of the tSQLt.Run... procedures to execute your test cases, you -can call tSQLt.XmlResultFormatter to display the results in XML. The format is -compatible with CruiseControl and can be merged into a build log the same way -that a JUnit test report is merged. +tSQLt Release Notes +=================== +An online version of these release notes is available at: +http://tsqlt.org/category/release-notes/ + +--------------------------- +Release: V1.1.8738.27883 +Date: 2022-02-16 + +BUG FIXES: + +1. tSQLt does not install on case sensitive *server*: https://github.com/tSQLt-org/tSQLt/issues/155 +2. Changed build order to eliminate warning about tSQLt.Private_MarktSQLt_TempObject: https://github.com/tSQLt-org/tSQLt/pull/151 + +NEW FEATURES: + +1. N/A + +MISCELLANEOUS: + +1. Microsoft changed the paths for sqlpackage and sqlcmd on the build agent image (windows-2019) + +--------------------------- +Release: V1.0.8043.39707 +Date: 2022-01-08 + +BUG FIXES: + +N/A + +NEW FEATURES: + +1. SpyProcedure has a new optional parameter, @CallOriginal +2. SpyProcedure provides the variable @SpyProcedureOriginalObjectName within the CommandToExecute +3. NoTransaction test annotation --[@tSQLt:NoTransaction](@CleanUpProcedureName) + This annotation causes the test to be executed outside of a transaction. The @CleanUpProcedureName procedure is executed after the test completes. +4. UndoTestDoubles, useful when test double procedures like tSQLt.FakeTable are executed outside of a transaction +5. FakeFunction allows FakeDataSource to be a "VALUES" clause + +MISCELLANEOUS: + +1. Reduced the complexity of building and testing tSQLt +2. Removed dependency on Private_NullCellTable table +3. Installation of tSQLt can handle more edge cases +4. Standardized error messages +5. For tSQLt contributors: New build target 'tSQLtOnly', which skips Facade validation steps + +--------------------------- +Release: V1.0.7950.1808 + +BUG FIXES: + +1. Removed unexpected facade objects (aka ufos) from tSQLt dacpacs. + +NEW FEATURES: + +N/A + +MISCELLANEOUS: + +N/A + +--------------------------- +Release: V1.0.7942.19440 + +BUG FIXES: + +N/A + +NEW FEATURES: + +1. tSQLt can now be installed using DACPACs as well as the original tSQLt.class.sql script. Note: Unlike the original tSQLt.class.sql script, DACPACs are SQL Server version specific. +2. tSQLt.FriendlySQLServerVersion function reports the SQL Server Version as a string with commonly used moniker e.g. 2008R2 or 2019. +3. tSQLt is now certified for SQL Server on Linux. + +MISCELLANEOUS: + +1. Created GitHub Action for building and testing tSQLt on MSSQL 2017 and 2019 using Redgate Spawn. +2. Refactored Azure DevOps Pipeline to use more powerful VMs and azcli to create the SQLVM with a bicep file. +3. Migrated parts of the tSQLt build from ant to PowerShell. +4. Prototyped using AKS (Azure Kubernetes Service) to host MSSQL 2014, 2016, 2017, and 2019. +5. Prototyped Facade dacpacs for use in Visual Studio when developing tests with tSQLt. Ultimately we chose a full tSQLt dacpac for this work, but continue to retain the original Facade code and tests. + + +--------------------------- +Release: V1.0.7682.21917 + +BUG FIXES: + +1. Fix sysname casing for case-sensitive databases. Fixed by Trygve Wastvedt (https://github.com/twastvedt). + +2. Fixed issue in tSQLt.Private_GetFullTypeName not handling fractional seconds precision on date and time types. Fixed by saperry (https://github.com/saperry). + +NEW FEATURES: + +1. FakeFunction now accepts non-functions as data sources for example tables or SELECT statements. Contributed by Dmitrij Kultasev (https://github.com/dkultasev). + +MISCELLANEOUS: + +1. For tSQLt contributors: allow SQL Server Authentication for build + +--------------------------- +Release: V1.0.7597.5637 + +BUG FIXES: + +N/A + +NEW FEATURES: + +1. The tSQLt CLR is now signed with a new key: + + Public key (hash algorithm: sha1): + 0024000004800000940000000602000000240000525341310004000001000100b9af416ad8dfed + ec08a5652fa257f1242bf4ed60ef5a7b84a429604d62c919c5663a9c7710a7c5df9953b69ec89f + ce85d71e051140b273f4c9bf890a2bc19c48f22d7b1f1d739f90eebc5729555f7f8b63ed088bbb + 083b336f7e38b92d44cfe1c842f09632b85114772ff2122bc638c78d497c4e88c2d656c166050d + 6e1ef394 + + Public key token is e8fff6f136d7b53e + +2. Several improvements to the build process, including switching from NAnt to Ant + +3. New prepare server process (this fixes all installation issues with 2017 and 2019) + + Before installing tSQLt, you can now run the PrepareServer.sql that is part of the tSQLt.zip download. + It automatically enables CLR and installs a server certificate that allows the installation of the CLR. + There is no need to disable strict CLR security on the server nor do you need to modify database security settings. + Executing the script requires SA permissions, but needs to be done only once per server. + + Reminder: If you are using the tSQLt.NewConnection feature, + you need to execute tSQLt.EnableExternalAccess after each installation of tSQLt. + +4. Annotations + + tSQLt now allows for test case annotations to modify test behavior. The annotations implemented so far are: + + --[@tSQLt:MaxSqlMajorVersion](@MaxVersion) <-- Skips the test if the major version of SQL Server is > @MaxVersion + --[@tSQLt:MinSqlMajorVersion](@MinVersion) <-- Skips the test if the major version of SQL Server is < @MinVersion + --[@tSQLt:Skip](@SkipReason) <-- Skips the test and reports @SkipReason as reason in the output + +5. Implemented a CI pipeline for tSQLt itself in Azure DevOps + +--------------------------- +Release: V1.0.5873.27393 + +BUG FIXES: + +1. Fixed error message in tSQLt.FakeTable + +2. tSQLt.DropClass now handles already quoted names correctly + +NEW FEATURES: + +1. The tSQLt CLR is now signed with a new key: + Public Key Token = 0x7722217d36028e4c + Public Key: 0x0602000000240000525341310004000001000100F7D9A45F2B508C2887A8794B053CE5DEB28743B7C748FF545F1F51218B684454B785054629C1417D1D3542B095D80BA171294948FCF978A502AA03240C024746B563BC29B4D8DCD6956593C0C425446021D699EF6FB4DC2155DE7E393150AD6617EDC01216EA93FCE5F8F7BE9FF605AD2B8344E8CC01BEDB924ED06FD368D1D0 + The password required to sign an assembly with this key is no longer part of the code + base. This was necessary to be able to provide a secure method of installing tSQLt not + requiring the database to be set to TRUSTWORTHY anymore. + +2. tSQLt.Info() now returns the public key token of the assembly's signing key in + the CLRSigningKey column + +3. tSQLt.RunNew executes all tests in test classes (schemata) that were created with + tSQLt.NewTestClass after the last call to tSQLt.Reset + +4. tSQLt.InstallExternalAccessKey installs the required objects in the master database + to allow tSQLt to execute with EXTERNAL_ACCESS without the database being TRUSTWORTHY + +5. tSQLt.RemoveExternalAccessKey removes those objects from the master database + +6. tSQLt.EnableExternalAccess can be used to manually enable and disable EXTERNAL_ACCESS + +7. tSQLt automatically tries to enable EXTERNAL_ACCESS, each time any "run" method is called. + Enabling EXTERNAL_ACCESS is possible when either the database is TRUSTWORTHY and owned + by a server principal with EXTERNAL_ACCESS_ASSEMBLY permission, or if + tSQLt.InstallExternalAccessKey has been executed before on the server. + + This feature establishes backward compatibillity but comes at a performance cost. + It might therefore be removed in a future version. + +8. tSQLt detects at the beginning of each execution, if the requirements to enable + EXTERNAL_ACCESS are not any longer fulfilled. If the assembly is still marked as + EXTERNAL_ACCESS, the execution is halted immediately, and an error is reported + back to the caller. + +9. tSQLt checks at the beginning of each execution, if the installed assembly's version + matches the version of the T-SQL code. If a mismatch is detected, the execution is + immediately halted and an error is reported back to the caller. + +A. tSQLt.Run now allows a test result formatter to be passed in in the @TestResultFormatter + parameter. If that parameter is omitted or NULL, the default result formatter is used. + +OTHER: + +1. Cleaned up several procedures and tests + +2. Removed extraneous DROP statements from tSQLt install file. + +--------------------------- +Release: V1.0.5793.20044 + +BUG FIXES: +1. AssertEqualsTableSchema now handles gaps in column_id values correctly (thanks Greg L.) +2. Fixed handling of empty messages in AssertEmptyTable + +NEW FEATURES: +1. AssertStringIn asserts that a string value is element of a set of string values +2. ApplyConstraint can now apply cascading actions on foreign keys +3. FakeTable can now fake synonyms of tables and view (in the same database only, for now) + +OTHER: + +--------------------------- +Release: V1.0.5686.18945 + +BUG FIXES: +1. The XML output now validates against the JUnit test result XML schema at + https://raw.githubusercontent.com/windyroad/JUnit-Schema/master/JUnit.xsd + While there is no "official" JUnit schema, the above is the one that is + referenced most often online. + +2. tSQLt.DropClass now handles XML schemata correctly. + +3. All tSQLt assertions can now be called with a @Message parameter. In case of a + failure, the value of that parameter will be output before the default failure + message. + For backward compatibility, tSQLt.AssertEqualsTable still has a @FailMsg + parameter. Its use is now deprecated. + +NEW FEATURES: +1. tSQLt.Info() now returns the version and build of the SQL Server Instance it is installed on: + + SELECT * FROM tSQLt.Info() AS I; + + Version ClrVersion SqlVersion SqlBuild + -------------- -------------- ---------- -------- + 1.0.5479.30419 1.0.5479.30419 12.00 4213.00 + +2. Verbose execution mode + Executing EXEC tSQLt.SetVerbose @Verbose = 1; before running the tests will cause + tSQLt to output the test name at the beginning and the end of each test's execution. + That makes it easier in large test suites to find the output of a particular test. + +3. tSQLt.RunC + The new procedure tSQLt.RunC behaves identical to tSQLt.Run. However, instead of + expecting the test (class) name in a parameter, it parses the INPUTBUFFER and + extracts the name from a specially formed comment: + + EXEC tSQLt.RunC;--Run_Methods_Tests.[test tSQLt.RunC calls tSQLt.Run with everything after ;-- as @TestName] + + This makes for a more powerful SQL Query shortcut in SSMS as test names now do not have to be quoted anymore. + +4. tSQLt.AssertEqualsTableSchema + tSQLt.AssertEqualsTableSchema is called like tSQLt.AssertEqualsTable. Instead of + the table contents, it compares the columns including name, datatype, collation, + NULL-ability and identity property. + +5. The test runner now captures start and end time for each test. This information + is included in the XML output. The default output contains the execution duration + for each test. + +6. Both tSQLt.SpyProcedure and tSQLt.FakeFunction now handle table type parameters. + The content of a table type parameter in a spied procedure is converted into + XML and included in the _SpyProcedureLog table. + +OTHER: + +1. Farewell Sourceforge + Because of several issues over the last months and because of even more complaints + about Sourceforge's business practices by tSQLt users, we decided to find a more appropriate space. + The official downloads are now available directly on tSQLt.org. The source code + repository will find its new home either on github or bitbucket over the next few days. + +2. The fail message of tSQLt.AssertEqualsString is now broken into two lines + with aligned string values for easier comparison. + +3. The installation script now prints a welcome message. + + + +--------------------------- +Release: V1.0.5325.27056 + +BUG FIXES: +1. FakeTable now handles CHAR UDTs + +NEW FEATURES: +1. SQL 2014 now officially supported +2. tSQLt.ApplyConstraint now handles UNIQUE and PRIMARY KEY constraints +3. tSQLt.DropClass now handles UDTs +4. tSQLt.ExpectNoException can be followed by tSQLt.ExpectException +5. Added @IfExists parameter to tSQLt.RemoveObject +6. Added tSQLt.RemoveObjectIfExists +7. Added tSQLt.AssertObjectDoesNotExist + +--------------------------- +Release: V1.0.5137.39257 + +NEW FEATURES: +1. added tSQLt.RenameClass + + +--------------------------- + +Release: V1.0.5071.16906 + +NEW FEATURES: +1. added tSQLt.FakeFunction +2. added @ExpectedErrorNumber parameter to tSQLt.ExpectException + + +--------------------------- + +Release: V1.0.4969.33062 + +NEW FEATURES: +1. tSQLt.ApplyTrigger +2. tSQLt.ExpectNoException +OTHER: +1. The output of a test that errors out now contains the severity and state of that error. + +--------------------------- + +Release: V1.0.4941.23369 + +BUG FIXES: +1. tSQLt.Fail now works when transaction is in an uncommittable state. +NEW FEATURES: +1. tSQLt.AssertNotEquals +2. tSQLt.AssertEmptyTable +3. tSQLt.ExpectException +OTHER: +1. We have now a set of snippets for Red Gate SQL Prompt to support test development + Instructions of how to get them are available on our downloads page: http://tSQLt.org/downloads + +--------------------------- + +Release: V1.0.4822.19862 + +BUG FIXES: +1. Corrected defect in AssertEqualsTable where the custom error message was not being displayed. +NEW FEATURES: +1. Added tSQLt.RemoveObject procedure. + +--------------------------- + +Release: V1.0.4735.30771 + +BUG FIXES: +1. AssertEqualsTable did not quote columns. +NEW FEATURES: +1. Added AssertLike procedure. +2. Added support for multi-column foreign keys in ApplyConstraint. + +--------------------------- + +Release: V1.0.4721.29450 + +BUG FIXES: +1. When executing tSQLt.Run on an individual test, the setup procedure was sometimes not + called based on the case of the setup procedure name. +OTHER: +1. Added StubRecord procedure back in, however it is marked as 'not supported'. + +--------------------------- + +Release: V1.0.4643.26915 + +OTHER: +1. Removed unused procedure: StubRecord +2. Improved the performance of AssertEqualsTable +3. AssertEqualsTable now gives reasonalbe error messages for unsupported datatypes. More + information on unsupported datatypes is available on the documentation page for + AssertEqualsTable: http://tsqlt.org/user-guide/assertions/assertequalstable/ + +--------------------------- +Release: V1.0.4504.21220 + +BUG FIXES: +1. tSQLt.NewTestClass now does not drop schema objects if the schema is not a test class + +--------------------------- +Release: V1.0.4496.29340 + +BUG FIXES: +1. tSQLt.ResultSetFilter now supports data types introduced in SQL Server 2008 and CLR + datatypes. +2. tSLQt.AssertResultSetsHaveSameMetaData now ignores "hidden" columns. For example, + when comparing the metadata of a view, this procedure had been including underlying + columns. + +--------------------------- +Release: V1.0.4462.23207 + +NEW FEATURES: +1. tSQLt.FakeTable now supports @Defaults parameter. If @Defaults = 1 default + constraints will be preserved on columns. +2. tSQLt.FakeTable now supports @ComputedColumns parameter. If + @ComputedColumns = 1 computed columns will be preserved. + +--------------------------- + +Release: 1.0.4413.31717 + +NEW FEATURES: +1. tSQLt.Info() now reports Version and CLRVersion. These should always match! +2. tSQLt.FakeTable now supports @Identity parameter. If @Identity = 1 the + identity property of the table is preserved. + +--------------------------- + +Release: V1.0.4357.27914 + +BUG FIXES: +1. tSQLt.SpyProcedure can handle user defined types. User defined types may be in schemas and + may also be not nullable. + +NEW FEATURES: +1. SetUp can be named in any combination of upper or lower case characters (e.g. setup, SETUP, + SeTuP, etc). +--------------------------- + +Release: V1.0.4351.28410 + +BUG FIXES: +1. tSQLt.NewTestClass now handles schema names with spaces and other special characters +2. tSQLt.NewTestClass now handles if a quoted name is passed for the new schema name + +NEW FEATURES: +1. tSQLt.Uninstall removes tSQLt from the database +2. tSQLt.RunWithXmlResults executes like tSQLt.Run, but produces results is XML +3. tSQLt.TestClasses is a view that lists properties of test classes +4. tSQLt.Tests is a view that lists properties of test cases +5. Example.sql includes an example database and is referenced by the updated Quick Start + (http://tsqlt.org/quick-start) +6. tSQLt.Info() provides information about the installed version of tSQLt. + +OTHER: +1. New numbering system for the tSQLt builds. The current version is: V1.0.4351.28410 +--------------------------- + +build.12: + +BUG FIXES: +1. tSQLt.Fail now handles NULL values as parameters. +2. Corrected XmlResultFormatter to have root element of 'testsuites' instead of 'root'. +This improves compatibility with more continuous integration servers. + +NEW FEATURES: +1. FakeTable now handles a single parameter, combining the schema and table name. +This makes FakeTable more consistent with other methods in tSQLt. +2. ApplyConstraint now handles two parameters, combining the schema and table +name as the first parameter. This makes ApplyConstraint more consistent with other +methods in tSQLt. + +--------------------------- + +build.11: + +BUG FIXES: +1. ApplyConstraint for a Foreign Key that references a Faked Table is fixed. +2. SetClrEnabled.sql utlity file now handles database names with spaces. +3. Test cases whose names contain a percent sign are now displayed correctly +in the test case output. + +NEW FEATURES: +1. Warnings about renaming objects displayed when executing FakeTable or +ApplyConstraint are now hidden. +2. New method: tSQLt.SuppressOutput suppresses the console output that would +be displayed by executing a command. +3. New method: tSQLt.CaptureOutput logs the console output from executing a +command into the tSQLt.CaptureOutputLog table. + +--------------------------- + +build.10: + +BUG FIXES: +1. Standardized capitalization of table, column, procedure and function names. + +NEW FEATURES: +1. Added support for case sensitive databases. + +--------------------------- + +build.9: + +BUG FIXES: +1. When using tSQLt.Run or tSQLt.RunTestClass, if an object name on the dbo +schema had the same name as a test class, the test class could not be executed. + +2. If the output of an AssertTableEquals or the length of a test case name was +too long, an error was be produced. + +NEW FEATURES: +1. A new procedure, tSQLt.NewConnection, was added which allows statements to +be executed synchronously in a different connection context. + +--------------------------- + +build.8a: + +BUG FIXES: +1. When executing tSQLt.ResultSetFilter, if the result set metadata contained +hidden columns then those columns would be returned in the result set output. +The values of those columns would be null. Hidden columns are typically present +when the base tables of a result set contain primary keys or other constraints +which were not selected. tSQLt.ResultSetFilter has been updated to not return +the hidden columns. + +--------------------------- + +build.8: + +NEW FEATURES: +1. SpyProcedure now supports output parameters. Details are available in the +User Guide. See: http://www2.sqlity.net/tsqlt/spyprocedure + +2. The first major sections of the User Guide are now available at: +http://www.tsqlt.org - On the right hand side is a link for the User Guide. + +--------------------------- + +build.7: + +IMPORTANT: tSQLt now utilizes CLR (Common Language Runtime) stored procedures. +In order to install tSQLt, CLRs must be enabled in SQL Server. The +SetClrEnabled.sql file has the command which enables CLRs. If CLRs are not +already enabled, this command must be executed before installing tSQLt. + +NEW FEATURES: +1. A new procedure tSQLt.RunAll, executes all test classes created with the +tSQLt.NewTestClass procedure. + +2. Added procedure tSQLt.AssertResultSetsHaveSameMetaData which allows the +meta data of two result sets to be compared. This compares several properties +of each column of the result set including the column name, data type, length, +precision, scale and other properties. + +3. Added procedure tSQLt.ResultSetFilter which returns a single result set from +a statement which produces multiple result sets. For example, you want to test +a stored procedure which executes several select statements. You can now use +ResultSetFilter to choose which result set to emit, and therefore you can +capture that result set into a table for use with tSQLt.AssertEqualsTable. + +4. The results of running tests can now be output in an XML format. After +calling one of the tSQLt.Run... procedures to execute your test cases, you +can call tSQLt.XmlResultFormatter to display the results in XML. The format is +compatible with CruiseControl and can be merged into a build log the same way +that a JUnit test report is merged. diff --git a/Build/SQL/Add(tSQLt.Built)ToExampleDB.sql b/Build/SQL/Add(tSQLt.Built)ToExampleDB.sql new file mode 100644 index 000000000..145c7b594 --- /dev/null +++ b/Build/SQL/Add(tSQLt.Built)ToExampleDB.sql @@ -0,0 +1,5 @@ +GO +CREATE USER [tSQLt.Build] FROM LOGIN [tSQLt.Build]; +GO +ALTER ROLE db_owner ADD MEMBER [tSQLt.Build]; +GO \ No newline at end of file diff --git a/Build/ChangeDbAndExecuteStatement(tSQLt.Build).sql b/Build/SQL/ChangeDbAndExecuteStatement(tSQLt.Build).sql similarity index 100% rename from Build/ChangeDbAndExecuteStatement(tSQLt.Build).sql rename to Build/SQL/ChangeDbAndExecuteStatement(tSQLt.Build).sql diff --git a/Build/SQL/CreateBuildDbBuildOrder.txt b/Build/SQL/CreateBuildDbBuildOrder.txt new file mode 100644 index 000000000..af193f625 --- /dev/null +++ b/Build/SQL/CreateBuildDbBuildOrder.txt @@ -0,0 +1,2 @@ +ForceDropTempProcedures.sql +CreateBuildDbStub.sql \ No newline at end of file diff --git a/Build/SQL/CreateBuildDbStub.sql b/Build/SQL/CreateBuildDbStub.sql new file mode 100644 index 000000000..8b699b74c --- /dev/null +++ b/Build/SQL/CreateBuildDbStub.sql @@ -0,0 +1,12 @@ +GO +EXEC #ForceDropLogin [$(NewDbName)]; +EXEC #ForceDropDatabase [$(NewDbName)]; +GO +CREATE DATABASE [$(NewDbName)] COLLATE SQL_Latin1_General_CP1_CS_AS WITH TRUSTWORTHY OFF; +-- COLLATE SQL_Latin1_General_CP1_CS_AS +GO +CREATE LOGIN [$(NewDbName)] WITH PASSWORD = 0x010095EBA5D9A28749DF6ABFD4F5AAFBCE8BD839E0E35D6273B4 HASHED, CHECK_POLICY = OFF, DEFAULT_DATABASE = tempdb; +GO +ALTER AUTHORIZATION ON DATABASE::[$(NewDbName)] TO [$(NewDbName)]; +GO +EXEC [$(NewDbName)].sys.sp_executesql N'CREATE USER [tSQLt.Build] FROM LOGIN [tSQLt.Build]; ALTER ROLE db_owner ADD MEMBER [tSQLt.Build];',N''; \ No newline at end of file diff --git a/Build/SQL/CreateLogin(tSQLt.Build).sql b/Build/SQL/CreateLogin(tSQLt.Build).sql new file mode 100644 index 000000000..b1fbd9a72 --- /dev/null +++ b/Build/SQL/CreateLogin(tSQLt.Build).sql @@ -0,0 +1,19 @@ +GO +SET NOCOUNT ON; +GO +USE master; +GO +EXEC #ForceDropLogin 'tSQLt.Build'; +GO +EXEC #ForceDropLogin 'tSQLt.Build.SA'; +GO +CREATE LOGIN [tSQLt.Build] WITH PASSWORD = 0x010095EBA5D9A28749DF6ABFD4F5AAFBCE8BD839E0E35D6273B0 HASHED, CHECK_POLICY = OFF, DEFAULT_DATABASE = tempdb; +GO +CREATE LOGIN [tSQLt.Build.SA] WITH PASSWORD = 0x010095EBA5D9A28749DF6ABFD4F5AAFBCE8BD839E0E35D6273B2 HASHED, CHECK_POLICY = OFF, DEFAULT_DATABASE = tempdb; +GO +EXEC sys.sp_addsrvrolemember @loginame = N'tSQLt.Build.SA', @rolename = N'sysadmin' +EXEC sys.sp_addsrvrolemember @loginame = N'tSQLt.Build', @rolename = N'dbcreator' +GO +EXEC master.sys.sp_executesql N'IF USER_ID(''tSQLt.Build'') IS NOT NULL DROP USER [tSQLt.Build];'; +EXEC master.sys.sp_executesql N'CREATE USER [tSQLt.Build] FROM LOGIN [tSQLt.Build];'; +GO diff --git a/Build/SQL/DisableExternalAccess.sql b/Build/SQL/DisableExternalAccess.sql new file mode 100644 index 000000000..4f05b11b2 --- /dev/null +++ b/Build/SQL/DisableExternalAccess.sql @@ -0,0 +1,4 @@ +IF(EXISTS(SELECT 1 FROM tSQLt.Info() WHERE HostPlatform NOT IN ('Linux'))) +BEGIN + EXEC tSQLt.EnableExternalAccess @try = 0, @enable=0; +END; diff --git a/Build/Drop(master.tSQLt_testutil).sql b/Build/SQL/Drop(master.tSQLt_testutil).sql similarity index 69% rename from Build/Drop(master.tSQLt_testutil).sql rename to Build/SQL/Drop(master.tSQLt_testutil).sql index 1ec6f8840..1494e2da9 100644 --- a/Build/Drop(master.tSQLt_testutil).sql +++ b/Build/SQL/Drop(master.tSQLt_testutil).sql @@ -1,5 +1,5 @@ IF OBJECT_ID('master.tSQLt_testutil.tSQLtTestUtil_DropExternalAccessKey') IS NOT NULL EXEC master.sys.sp_executesql N'DROP PROCEDURE tSQLt_testutil.tSQLtTestUtil_DropExternalAccessKey;'; -IF OBJECT_ID('master.tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke') IS NOT NULL EXEC master.sys.sp_executesql N'DROP PROCEDURE tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke;'; +IF OBJECT_ID('master.tSQLt_testutil.tSQLtTestUtil_UnsafeAssemblyAndExternalAccessRevoke') IS NOT NULL EXEC master.sys.sp_executesql N'DROP PROCEDURE tSQLt_testutil.tSQLtTestUtil_UnsafeAssemblyAndExternalAccessRevoke;'; EXEC master.sys.sp_executesql N'IF SCHEMA_ID(''tSQLt_testutil'') IS NOT NULL DROP SCHEMA tSQLt_testutil;'; EXEC master.sys.sp_executesql N'IF SUSER_ID(''tSQLt_testutil'') IS NOT NULL DROP LOGIN tSQLt_testutil;'; EXEC master.sys.sp_executesql N'IF CERT_ID(''tSQLt_testutil'') IS NOT NULL DROP CERTIFICATE tSQLt_testutil;'; diff --git a/Build/SQL/Drop(tSQLtAssemblyKey)(Pre2017).sql b/Build/SQL/Drop(tSQLtAssemblyKey)(Pre2017).sql new file mode 100644 index 000000000..b6f759a30 --- /dev/null +++ b/Build/SQL/Drop(tSQLtAssemblyKey)(Pre2017).sql @@ -0,0 +1,14 @@ +DECLARE @cmd NVARCHAR(MAX); + +IF(CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(MAX)),4) AS INT)<14) +BEGIN + PRINT '-------Dropping tSQLtAssemblyKey---------------'; + SET @cmd = N'IF SUSER_ID(''tSQLtAssemblyKey'') IS NOT NULL DROP LOGIN tSQLtAssemblyKey;'; + EXEC master.sys.sp_executesql @cmd; + SET @cmd = N'IF ASYMKEY_ID(''tSQLtAssemblyKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtAssemblyKey;'; + EXEC master.sys.sp_executesql @cmd; +END; +ELSE +BEGIN + PRINT '-------Dropping of tSQLtAssemblyKey skipped---------------'; +END; diff --git a/Build/SQL/EnableExternalAccess.sql b/Build/SQL/EnableExternalAccess.sql new file mode 100644 index 000000000..2b1223f7f --- /dev/null +++ b/Build/SQL/EnableExternalAccess.sql @@ -0,0 +1,4 @@ +IF(EXISTS(SELECT 1 FROM tSQLt.Info() WHERE HostPlatform NOT IN ('Linux'))) +BEGIN + EXEC tSQLt.EnableExternalAccess @try = 0, @enable=1; +END; diff --git a/Build/ExecuteAs(tSQLt.Build).sql b/Build/SQL/ExecuteAs(tSQLt.Build).sql similarity index 100% rename from Build/ExecuteAs(tSQLt.Build).sql rename to Build/SQL/ExecuteAs(tSQLt.Build).sql diff --git a/Build/ExecuteAs(tSQLt.Build.SA).sql b/Build/SQL/ExecuteAs(tSQLt.Build.SA).sql similarity index 100% rename from Build/ExecuteAs(tSQLt.Build.SA).sql rename to Build/SQL/ExecuteAs(tSQLt.Build.SA).sql diff --git a/Build/SQL/ExecuteAsCleanup.sql b/Build/SQL/ExecuteAsCleanup.sql new file mode 100644 index 000000000..c98e6cfe2 --- /dev/null +++ b/Build/SQL/ExecuteAsCleanup.sql @@ -0,0 +1,18 @@ +GO +DECLARE @Counter INT = 0; +DECLARE @MaxAttempts INT = 10; +DECLARE @PreviousUser NVARCHAR(128) = SYSTEM_USER; + +WHILE ORIGINAL_LOGIN() <> SYSTEM_USER AND @Counter < @MaxAttempts +BEGIN + SET @Counter=@Counter+1; + REVERT; + SELECT @Counter=0,@PreviousUser=SYSTEM_USER WHERE @PreviousUser<>SYSTEM_USER; + PRINT SYSTEM_USER +END + +IF @Counter >= @MaxAttempts +BEGIN + RAISERROR('WARNING: Impersonation could not be reverted after %d attempts.', 0, 1, @MaxAttempts) WITH NOWAIT; +END +GO \ No newline at end of file diff --git a/Build/SQL/ExecuteResetValidationServer.sql b/Build/SQL/ExecuteResetValidationServer.sql new file mode 100644 index 000000000..4d5172487 --- /dev/null +++ b/Build/SQL/ExecuteResetValidationServer.sql @@ -0,0 +1,13 @@ + GO + EXEC #RemoveAssemblyKey; + GO + EXEC master.sys.sp_configure @configname='show advanced options', @configvalue = 1; + GO + RECONFIGURE; + GO + IF(CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(MAX)),4) AS INT)>=14) EXEC master.sys.sp_configure @configname='clr strict security', @configvalue = 1; + EXEC master.sys.sp_configure @configname='clr enabled', @configvalue = 0; + GO + RECONFIGURE; + GO + \ No newline at end of file diff --git a/Build/SQL/ForceDropTempProcedures.sql b/Build/SQL/ForceDropTempProcedures.sql new file mode 100644 index 000000000..65cf63f32 --- /dev/null +++ b/Build/SQL/ForceDropTempProcedures.sql @@ -0,0 +1,55 @@ +IF OBJECT_ID('tempdb..#ForceDropDatabase') IS NOT NULL DROP PROCEDURE #ForceDropDatabase; +GO +CREATE PROCEDURE #ForceDropDatabase +@db_name NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + IF(DB_ID(@db_name)IS NOT NULL) + BEGIN + BEGIN TRY + SET @cmd = ' + USE master; + ALTER DATABASE '+QUOTENAME(@db_name)+' SET ONLINE WITH ROLLBACK IMMEDIATE; + ALTER DATABASE '+QUOTENAME(@db_name)+' SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE; + USE '+QUOTENAME(@db_name)+'; + ALTER DATABASE '+QUOTENAME(@db_name)+' SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + '; + EXEC(@cmd); + END TRY BEGIN CATCH END CATCH; + SET @cmd = ' + USE master; + DROP DATABASE '+QUOTENAME(@db_name)+'; + '; + EXEC(@cmd); + END; +END +GO +IF OBJECT_ID('tempdb..#ForceDropLogin') IS NOT NULL DROP PROCEDURE #ForceDropLogin; +GO +CREATE PROCEDURE #ForceDropLogin +@login_name NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd_dd NVARCHAR(MAX) = + ( + SELECT 'EXEC #ForceDropDatabase '+QUOTENAME(D.name,'''')+';' + FROM sys.databases AS D + JOIN sys.server_principals AS SP + ON D.owner_sid = SP.sid + WHERE SP.name = @login_name + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd_dd); + IF SUSER_SID(@login_name) IS NOT NULL + BEGIN + DECLARE @cmd_dl NVARCHAR(MAX) = ( + SELECT 'KILL '+CAST(session_id AS NVARCHAR(MAX))+';' + FROM sys.dm_exec_sessions WHERE login_name = @login_name + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + SET @cmd_dl = ISNULL(@cmd_dl,'') + 'DROP LOGIN '+QUOTENAME(@login_name)+';' + EXEC(@cmd_dl); + END +END +GO diff --git a/Build/GetFailedTestCount.sql b/Build/SQL/GetFailedTestCount.sql similarity index 88% rename from Build/GetFailedTestCount.sql rename to Build/SQL/GetFailedTestCount.sql index f60e29494..ec3448245 100644 --- a/Build/GetFailedTestCount.sql +++ b/Build/SQL/GetFailedTestCount.sql @@ -1 +1,2 @@ -:EXIT(SELECT COUNT(*) FROM tSQLt.TestResult WHERE Result != 'Success') \ No newline at end of file +:EXIT(SELECT COUNT(*) FROM tSQLt.TestResult WHERE Result != 'Success') + diff --git a/Build/SQL/GetFriendlySQLServerVersion.template.sql b/Build/SQL/GetFriendlySQLServerVersion.template.sql new file mode 100644 index 000000000..895fc030a --- /dev/null +++ b/Build/SQL/GetFriendlySQLServerVersion.template.sql @@ -0,0 +1,12 @@ +DECLARE @ProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + DECLARE @FriendlyVersion NVARCHAR(128) = (SELECT FriendlyVersion + FROM + ( + /*snip1content*/ + FROM + ( + /*snip2content*/ + )SSV + )X + ); + SELECT @FriendlyVersion FriendlyVersion; \ No newline at end of file diff --git a/Build/SQL/GetSQLServerVersion.sql b/Build/SQL/GetSQLServerVersion.sql new file mode 100644 index 000000000..8d52a74a4 --- /dev/null +++ b/Build/SQL/GetSQLServerVersion.sql @@ -0,0 +1,13 @@ +SELECT + CASE PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR(MAX)),4) + WHEN 10 THEN + CASE PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR(MAX)),3) + WHEN 0 THEN '2008' ELSE '2008R2' + END + WHEN 11 THEN '2012' + WHEN 12 THEN '2014' + WHEN 13 THEN '2016' + WHEN 14 THEN '2017' + WHEN 15 THEN '2019' + ELSE 'Unknown' + END SQLVersion; \ No newline at end of file diff --git a/Build/GrantBuildPermissions.sql b/Build/SQL/GrantBuildPermissions.sql similarity index 100% rename from Build/GrantBuildPermissions.sql rename to Build/SQL/GrantBuildPermissions.sql diff --git a/Build/Install(master.tSQLt_testutil).sql b/Build/SQL/Install(master.tSQLt_testutil).sql similarity index 65% rename from Build/Install(master.tSQLt_testutil).sql rename to Build/SQL/Install(master.tSQLt_testutil).sql index 667e9295d..a83173d45 100644 --- a/Build/Install(master.tSQLt_testutil).sql +++ b/Build/SQL/Install(master.tSQLt_testutil).sql @@ -1,8 +1,8 @@ EXEC master.sys.sp_executesql N'IF SCHEMA_ID(''tSQLt_testutil'') IS NULL EXEC(''CREATE SCHEMA tSQLt_testutil;'');'; -EXEC master.sys.sp_executesql N'CREATE PROCEDURE tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke AS REVOKE EXTERNAL ACCESS ASSEMBLY TO [tSQLtExternalAccessKey];'; +EXEC master.sys.sp_executesql N'CREATE PROCEDURE tSQLt_testutil.tSQLtTestUtil_UnsafeAssemblyAndExternalAccessRevoke AS REVOKE EXTERNAL ACCESS ASSEMBLY,UNSAFE ASSEMBLY TO [tSQLtAssemblyKey];'; EXEC master.sys.sp_executesql N'CREATE CERTIFICATE tSQLt_testutil ENCRYPTION BY PASSWORD = ''CE4E37C2-A9B6-409D-94C3-F051FB09D957DA704F07-2F5D-4C4A-8497-A9A10C6E51F4'' WITH SUBJECT=''tSQLt_testutil'';'; CREATE LOGIN tSQLt_testutil FROM CERTIFICATE tSQLt_testutil; EXEC sys.sp_addsrvrolemember @loginame = N'tSQLt_testutil', @rolename = N'sysadmin'; -EXEC master.sys.sp_executesql N'ADD SIGNATURE TO tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke BY CERTIFICATE tSQLt_testutil WITH PASSWORD = ''CE4E37C2-A9B6-409D-94C3-F051FB09D957DA704F07-2F5D-4C4A-8497-A9A10C6E51F4'';'; -EXEC master.sys.sp_executesql N'GRANT EXECUTE ON OBJECT::tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke TO [tSQLt.Build];'; +EXEC master.sys.sp_executesql N'ADD SIGNATURE TO tSQLt_testutil.tSQLtTestUtil_UnsafeAssemblyAndExternalAccessRevoke BY CERTIFICATE tSQLt_testutil WITH PASSWORD = ''CE4E37C2-A9B6-409D-94C3-F051FB09D957DA704F07-2F5D-4C4A-8497-A9A10C6E51F4'';'; +EXEC master.sys.sp_executesql N'GRANT EXECUTE ON OBJECT::tSQLt_testutil.tSQLtTestUtil_UnsafeAssemblyAndExternalAccessRevoke TO [tSQLt.Build];'; EXEC master.sys.sp_executesql N'ALTER CERTIFICATE tSQLt_testutil REMOVE PRIVATE KEY;'; diff --git a/Build/SQL/Install(tSQLtAssemblyKey).sql b/Build/SQL/Install(tSQLtAssemblyKey).sql new file mode 100644 index 000000000..9133ae827 --- /dev/null +++ b/Build/SQL/Install(tSQLtAssemblyKey).sql @@ -0,0 +1 @@ +EXEC tSQLt.InstallAssemblyKey; diff --git a/Build/SQL/PrintVersionInfo.sql b/Build/SQL/PrintVersionInfo.sql new file mode 100644 index 000000000..0913d3242 --- /dev/null +++ b/Build/SQL/PrintVersionInfo.sql @@ -0,0 +1,17 @@ +DECLARE @txt NVARCHAR(MAX); +IF(OBJECT_ID('tempdb..#VersionInfoTable')IS NOT NULL)DROP TABLE #VersionInfoTable; +SELECT Version, ClrVersion, ClrSigningKey INTO #VersionInfoTable FROM tSQLt.Info(); +EXEC tSQLt.TableToText @txt = @txt OUTPUT, @TableName = N'#VersionInfoTable'; + +RAISERROR('+-----------------------------------------------------------------------------------------------------+',0,1)WITH NOWAIT; +RAISERROR('| tSQLt Info: |',0,1)WITH NOWAIT; +RAISERROR('+-----------------------------------------------------------------------------------------------------+',0,1)WITH NOWAIT; +RAISERROR('',0,1)WITH NOWAIT; +RAISERROR(@txt,0,1)WITH NOWAIT; +RAISERROR('',0,1)WITH NOWAIT; +RAISERROR('+-----------------------------------------------------------------------------------------------------+',0,1)WITH NOWAIT; +RAISERROR('| SQL Server Info: |',0,1)WITH NOWAIT; +RAISERROR('+-----------------------------------------------------------------------------------------------------+',0,1)WITH NOWAIT; +RAISERROR('',0,1)WITH NOWAIT; +RAISERROR('%s',0,1,@@VERSION)WITH NOWAIT; +RAISERROR('-------------------------------------------------------------------------------------------------------',0,1)WITH NOWAIT; diff --git a/Build/SQL/SeparatorTemplate.sql b/Build/SQL/SeparatorTemplate.sql new file mode 100644 index 000000000..4877980c5 --- /dev/null +++ b/Build/SQL/SeparatorTemplate.sql @@ -0,0 +1,4 @@ + +GO +--> /*--FILENAME--*/ +GO diff --git a/Build/SQL/UseTempDb.sql b/Build/SQL/UseTempDb.sql new file mode 100644 index 000000000..6aa4504c3 --- /dev/null +++ b/Build/SQL/UseTempDb.sql @@ -0,0 +1,3 @@ +GO +USE tempdb; +GO diff --git a/Build/SQLServerConnection.ps1 b/Build/SQLServerConnection.ps1 new file mode 100644 index 000000000..d65240772 --- /dev/null +++ b/Build/SQLServerConnection.ps1 @@ -0,0 +1,89 @@ +if (-not ([System.Management.Automation.PSTypeName]'SqlServerConnection').Type) { + + class SqlServerConnection { + hidden [string]$ServerName + hidden [string]$UserName + hidden [System.Security.SecureString]$Password + hidden [bool]$TrustedConnection + hidden [string]$ApplicationName + hidden [string]$BaseConnectionString = "Connect Timeout=60;TrustServerCertificate=true;" + + SqlServerConnection([string]$ServerName, [string]$UserName, [System.Security.SecureString]$Password,[string]$ApplicationName) { + $this.ServerName = $ServerName.Trim() + $this.UserName = $UserName.Trim() + $this.Password = $Password + $this.TrustedConnection = false + $this.ApplicationName = $ApplicationName.Trim() + } + # SqlServerConnection([string]$ServerName,[string]$ApplicationName) { + # $this.ServerName = $ServerName.Trim() + # $this.UserName = $null + # $this.Password = $null + # $this.TrustedConnection = true + # $this.ApplicationName = $ApplicationName.Trim() + # } + + [string] ToString() { + + + return @{ + ServerName= $($this.ServerName) + UserName= $($this.UserName) + Password= (ConvertFrom-SecureString $this.Password -AsPlainText) + TrustedConnection= $($this.TrustedConnection) + ApplicationName= $($this.ApplicationName) + BaseConnectionString= $($this.BaseConnectionString) + }| ConvertTo-Json -Compress + } + + static [string] ToStringStatic([SqlServerConnection]$instance) { + if ($null -eq $instance) { + return "$null" + } + else { + return $instance.ToString() + } + } + + [string] GetServerName() { + return $this.ServerName + } + + [string] GetUserName() { + return $this.UserName + } + + [System.Security.SecureString] GetPassword() { + return $this.Password + } + + [bool] GetTrustedConnection() { + return $this.TrustedConnection + } + + hidden [string] EscapeAndQuoteConnectionStringValue([string]$value) { + return "`"$( $value.Replace('"', '""') )`"" + } + + [string] GetConnectionString([string]$DatabaseName = '', [string]$ApplicationNameSuffix = '') { + $connectionString = "Server=$($this.EscapeAndQuoteConnectionStringValue($this.ServerName));" + + if ($this.TrustedConnection) { + $connectionString += "Integrated Security=SSPI;" + } else { + $connectionString += "User Id=$($this.EscapeAndQuoteConnectionStringValue($this.UserName));" + $connectionString += "Password=$($this.EscapeAndQuoteConnectionStringValue((ConvertFrom-SecureString $this.Password -AsPlainText)));" + } + if(![string]::IsNullOrWhiteSpace($DatabaseName)){ + $connectionString += "Initial Catalog=$($this.EscapeAndQuoteConnectionStringValue($DatabaseName));" + } + $PApplicationName = $this.ApplicationName; + if(![string]::IsNullOrWhiteSpace($ApplicationNameSuffix)){ + $PApplicationName+=".$ApplicationNameSuffix" + } + $connectionString += "Application Name=$($this.EscapeAndQuoteConnectionStringValue($PApplicationName));" + return $this.BaseConnectionString+$connectionString + } + } + } + \ No newline at end of file diff --git a/Build/Setup.bat b/Build/Setup.bat deleted file mode 100644 index 6578dfad7..000000000 --- a/Build/Setup.bat +++ /dev/null @@ -1,9 +0,0 @@ -@ECHO OFF -IF "%~1"=="" GOTO :Usage - -CD /d %~p0 -sqlcmd -S %1 -E -I -d master -i "ExecuteAsSA.sql" "tSQLtKey.sql" "Create(tSQLtKey).sql" "CreateLogin(tSQLt.Build).sql" -V11 -GOTO :EOF - -:Usage - ECHO Usage %~nx0 ServerAndInstanceName diff --git a/Build/SqlLoginNbuild.bat b/Build/SqlLoginNbuild.bat deleted file mode 100644 index d0665612e..000000000 --- a/Build/SqlLoginNbuild.bat +++ /dev/null @@ -1,14 +0,0 @@ -@ECHO OFF -%1\bin\nant -buildfile:tSQLt.build -D:msbuild.path=%2 || goto :error -%1\bin\nant -buildfile:tSQLt.validatebuild -D:db.version=%3 -D:db.server=%4 -D:db.user=%5 -D:db.password=%6 -D:db.name=%7 || goto :error - -ECHO +-----------------+ -ECHO : BUILD SUCCEEDED : -ECHO +-----------------+ -goto :EOF - -:error -ECHO +--------------+ -ECHO : BUILD FAILED : -ECHO +--------------+ -exit /b %errorlevel% \ No newline at end of file diff --git a/Build/VersionInfo.vbs b/Build/VersionInfo.vbs deleted file mode 100644 index e3f5f862b..000000000 --- a/Build/VersionInfo.vbs +++ /dev/null @@ -1,4 +0,0 @@ -set args = WScript.Arguments -Set fso = CreateObject("Scripting.FileSystemObject") -WScript.Echo fso.GetFileVersion(args(0)) -Wscript.Quit \ No newline at end of file diff --git a/Build/build.xml b/Build/build.xml deleted file mode 100644 index 85201144e..000000000 --- a/Build/build.xml +++ /dev/null @@ -1,263 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Build/buildNotes.txt b/Build/buildNotes.txt deleted file mode 100644 index b47fb107a..000000000 --- a/Build/buildNotes.txt +++ /dev/null @@ -1,43 +0,0 @@ -Setting up tSQLt build notes: -1. You must run MSBuild from the command line once before running the build. The reason is that the first time it prompts for the signing password. - -This gets somehow stored internally in Windows, and will not need to be re-entered again. - -Here is an example of the MSBuild command: -c:\Projects\tSQLt.sourceforge\tSQLtCLR\tSQLtCLR>C:/Windows/Microsoft.NET/Framework/v2.0.50727/msbuild.exe tSQLtCLR.csproj /p:Configuration=Release /nologo - -Note that the current path is where the tSQLtCLR.csproj file lives. - -2. There are parameters which need to be set for the ant script. They should be passed from the CruiseControl config file: \CruiseControl\config.xml -The parameters are: - - - - -3. Here is an example of the project within CruiseControl's config file: - - - - - - - - - - - - - - - - - - - - - - - - - -C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe tSQLtCLR.csproj /p:Configuration=CruiseControl /nologo \ No newline at end of file diff --git a/Build/buildtranslate.txt b/Build/buildtranslate.txt deleted file mode 100644 index a0eef3d68..000000000 --- a/Build/buildtranslate.txt +++ /dev/null @@ -1,8 +0,0 @@ -ant, nant -exec@executable, exec@program -exec@dir, exec@basedir, !interpreted differently -exec@outputproperty, exec@resultproperty - - -zip@destfile, zip@zipfile -fileset@file, fileset/include@name \ No newline at end of file diff --git a/Build/localNDeploy.bat b/Build/localNDeploy.bat deleted file mode 100644 index fb3ea23b6..000000000 --- a/Build/localNDeploy.bat +++ /dev/null @@ -1,14 +0,0 @@ -@ECHO OFF -%1\bin\nant -buildfile:tSQLt.build -D:msbuild.path=%2 || goto :error -%1\bin\nant -buildfile:tSQLt.validatebuild -D:db.version=%3 -D:db.server=%4 -D:db.name=%5 deploy.all || goto :error - -ECHO +-----------------+ -ECHO : BUILD SUCCEEDED : -ECHO +-----------------+ -goto :EOF - -:error -ECHO +--------------+ -ECHO : BUILD FAILED : -ECHO +--------------+ -exit /b %errorlevel% \ No newline at end of file diff --git a/Build/localNbuild.bat b/Build/localNbuild.bat deleted file mode 100644 index a4dd67614..000000000 --- a/Build/localNbuild.bat +++ /dev/null @@ -1,27 +0,0 @@ -@ECHO OFF - -ECHO +-------------------+ -ECHO : Starting BUILD : -ECHO +-------------------+ -%1\bin\nant -buildfile:tSQLt.build -D:msbuild.path=%2 || goto :error - -ECHO +-------------------+ -ECHO : Copying BUILD : -ECHO +-------------------+ -%1\bin\nant -buildfile:tSQLt.local_build_output.build || goto :error - -ECHO +-------------------+ -ECHO : Validating BUILD : -ECHO +-------------------+ -%1\bin\nant -buildfile:tSQLt.validatebuild -D:db.version=%3 -D:db.server=%4 -D:db.name=%5 || goto :error - -ECHO +-------------------+ -ECHO : BUILD SUCCEEDED : -ECHO +-------------------+ -goto :EOF - -:error -ECHO +-------------------+ -ECHO : BUILD FAILED : -ECHO +-------------------+ -exit /b %errorlevel% \ No newline at end of file diff --git a/Build/Import-PfxCert.ps1 b/Build/reviewneeded/Import-PfxCert.ps1 similarity index 100% rename from Build/Import-PfxCert.ps1 rename to Build/reviewneeded/Import-PfxCert.ps1 diff --git a/Build/reviewneeded/Install the tSQLt build.docx b/Build/reviewneeded/Install the tSQLt build.docx new file mode 100644 index 000000000..1b949123f Binary files /dev/null and b/Build/reviewneeded/Install the tSQLt build.docx differ diff --git a/Build/NewRelease.doc b/Build/reviewneeded/NewRelease.doc similarity index 100% rename from Build/NewRelease.doc rename to Build/reviewneeded/NewRelease.doc diff --git a/Build/reviewneeded/Setup.bat b/Build/reviewneeded/Setup.bat new file mode 100644 index 000000000..4e89f14c5 --- /dev/null +++ b/Build/reviewneeded/Setup.bat @@ -0,0 +1,10 @@ +@ECHO OFF +IF "%~1"=="" GOTO :Usage + +CD /d %~p0 +REM sqlcmd -S %1 -E -I -d master -i "ExecuteAsSA.sql" "tSQLtKey.sql" "Create(tSQLtKey).sql" "CreateLogin(tSQLt.Build).sql" -V11 +sqlcmd -S %1 -E -I -d master -i "CreateLogin(tSQLt.Build).sql" -V11 +GOTO :EOF + +:Usage + ECHO Usage %~nx0 ServerAndInstanceName diff --git a/Build/tSQLt.build b/Build/tSQLt.build deleted file mode 100644 index fc96ac43e..000000000 --- a/Build/tSQLt.build +++ /dev/null @@ -1,324 +0,0 @@ - - - Build tSQLt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Build/tSQLt.local_build_output.build b/Build/tSQLt.local_build_output.build deleted file mode 100644 index 1b8a68777..000000000 --- a/Build/tSQLt.local_build_output.build +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Build/tSQLt.validatebuild b/Build/tSQLt.validatebuild deleted file mode 100644 index 99bbdbfa7..000000000 --- a/Build/tSQLt.validatebuild +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Build/tSQLt_Build.ps1 b/Build/tSQLt_Build.ps1 new file mode 100644 index 000000000..061c973a1 --- /dev/null +++ b/Build/tSQLt_Build.ps1 @@ -0,0 +1,143 @@ +using module "./CommonFunctionsAndMethods.psm1"; + +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +try{ + + $buildPath = $invocationDir; + $sourcePath = (Join-Path $invocationDir '../Source' | Resolve-Path); + + $OutputPath = "./output/tSQLtBuild/"; + $TempPath = "./temp/tSQLtBuild/"; + + <# Clean #> + Remove-DirectoryQuietly -Path $TempPath; + Remove-DirectoryQuietly -Path $OutputPath; + <# Init directories, capturing the return values in a variable so that they don't print. #> + $__ = New-Item -ItemType "directory" -Path $TempPath; + $__ = New-Item -ItemType "directory" -Path $OutputPath; + + Log-Output("Createting DROP CLASS statement..") + ./tSQLt_Build/CreateDropClassStatement.ps1 + + Log-Output("Copying files...") + $fileList = ("License.txt","ReleaseNotes.txt") + $fileList|ForEach-Object{(Join-Path $buildPath $_ | Resolve-Path)|Copy-Item -Destination $TempPath} + + Log-Output("Unzipping tSQLtCLR DLLs...") + Expand-Archive ./output/tSQLtCLR/tSQLtCLR.zip -DestinationPath ./temp/tSQLtBuild/tSQLtCLR + + Log-Output("Generating CREATE ASSEMBLY statement...") + $tSQLtCLRDLLPath = (Join-Path $TempPath "tSQLtCLR/tSQLtCLR.dll") | Resolve-Path + $tSQLtCLRSQLPath = (Join-Path $sourcePath "tSQLtCLR.mdl.sql") | Resolve-Path + $tSQLtCLROutputPath = (Join-Path $TempPath "CreateAssembly.sql") + ./tSQLt_Build/CreateAssemblyGenerator.ps1 $tSQLtCLROutputPath $tSQLtCLRDLLPath $tSQLtCLRSQLPath 0x000000 "0xZZZZZ" 200 "'+`n'" + + Log-Output("Generating Version.txt...") + $getAssemblyInfoPath = (Join-Path $buildPath "tSQLt_Build/GetAssemblyInfo.ps1" | Resolve-Path) + $tSQLtVersion = & $getAssemblyInfoPath $tSQLtCLRDLLPath -v + Set-Content -Path (Join-Path $tempPath "Version.txt") -Value $tSQLtVersion; + Log-Output("+------------------------------------------------------+"); + $VersionOutput = "tSQLt Version: V$tSQLtVersion".PadRight(50,' '); + Log-Output("| $VersionOutput |"); + Log-Output("+------------------------------------------------------+"); + + Log-Output("Generating GetAssemblyKeyBytes function...") + $tSQLtCLRDLLPath = (Join-Path $TempPath "tSQLtCLR/tSQLtAssemblyKey.dll") | Resolve-Path + $tSQLtCLRSQLPath = (Join-Path $sourcePath "tSQLt.Private_GetAssemblyKeyBytes.mdl.sql") | Resolve-Path + $tSQLtCLROutputPath = (Join-Path $TempPath "tSQLt.Private_GetAssemblyKeyBytes.sql") + ./tSQLt_Build/CreateAssemblyGenerator.ps1 $tSQLtCLROutputPath $tSQLtCLRDLLPath $tSQLtCLRSQLPath 0x000000 0x000001 200 "+`n0x" + + Log-Output("Generating tSQLt.class.sql file...") + $tSQLtSeparatorPath = (Join-Path $buildPath "SQL/SeparatorTemplate.sql") | Resolve-Path + $tSQLtFileListPath = (Join-Path $sourcePath "BuildOrder.txt") | Resolve-Path + $tSQLtClassOutputPath = (Join-Path $TempPath "tSQLt.class.sql") + $replacements = @( + @{"s"='$LATEST-BUILD-NUMBER$';"r"=$tSQLtVersion} + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;} + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ); + ./tSQLt_Build/ConcatenateFiles.ps1 -OutputFile $tSQLtClassOutputPath -SeparatorTemplate $tSQLtSeparatorPath -InputPath $tSQLtFileListPath -Bracket "---Build" -Replacements $replacements + + Log-Output("Updating ReleaseNotes...") + $releaseNotesPath = (Join-Path $buildPath "ReleaseNotes.txt") | Resolve-Path + (Get-Content -Path $releaseNotesPath).Replace('LATEST-BUILD-NUMBER', $tSQLtVersion) | Set-Content -Path $releaseNotesPath + + Log-Output("Creating PrepareServer.sql...") + $PrepareServerSeparatorPath = (Join-Path $buildPath "SQL/SeparatorTemplate.sql") | Resolve-Path + $PrepareServerFileListPath = (Join-Path $sourcePath "PrepareServerBuildOrder.txt") | Resolve-Path + $PrepareServerOutputPath = (Join-Path $TempPath "PrepareServer.sql") + $PrepareServerReplacements = @( + @{'s'='tSQLt.';'r'='#';}, + @{'s'="OBJECT_ID('#";'r'="OBJECT_ID('tempdb..#";}, + @{'s'='---Build-';'r'='';}, + @{'s'='---Build+';'r'='';}, + @{'s'="(?m)^\s*--.*";'r'='';isRegex=$true;} + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;}, + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ); + ./tSQLt_Build/ConcatenateFiles.ps1 -OutputFile $PrepareServerOutputPath -SeparatorTemplate $PrepareServerSeparatorPath -InputPath $PrepareServerFileListPath -Replacements $PrepareServerReplacements + + Log-Output("Creating Example.sql...") + $ExampleSeparatorPath = (Join-Path $buildPath "SQL/SeparatorTemplate.sql") | Resolve-Path + $ExampleFileListPath = (Join-Path $buildPath "../Examples/BuildOrder.txt") | Resolve-Path + $ExampleOutputPath = (Join-Path $TempPath "Example.sql") + $ExampleReplacements = @( + @{'s'="(?m)^\s*---*$";'r'='';isRegex=$true;} + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;}, + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ); + ./tSQLt_Build/ConcatenateFiles.ps1 -OutputFile $ExampleOutputPath -SeparatorTemplate $ExampleSeparatorPath -InputPath $ExampleFileListPath -Replacements $ExampleReplacements + + Log-Output("Creating CommitId.txt...") + $CommitIdPath = (Join-Path $tempPath "CommitId.txt"); + (& git rev-parse HEAD)|Set-Content -Path $CommitIdPath; + Log-Output("+--------------------------------------------------------------------------+"); + $ComitIdOutput = ("Git Commit Id: "+(Get-Content -Path $CommitIdPath )).PadRight(70,' '); + Log-Output("| $ComitIdOutput |"); + Log-Output("+--------------------------------------------------------------------------+"); + + + + Log-Output("Creating GetFriendlySQLServerVersion.sql...") + $templateContent = Get-Content -path (Join-Path $buildPath "SQL/GetFriendlySQLServerVersion.template.sql"); + $sqlFile1Content = Get-Content -path (Join-Path $sourcePath "tSQLt.FriendlySQLServerVersion.sfn.sql"); + $sqlFile2Content = Get-Content -path (Join-Path $sourcePath "tSQLt.Private_SplitSqlVersion.sfn.sql"); + + $snip1Content = (Get-SnipContent $sqlFile1Content "/*StartSnip*/" "/*EndSnip*/"); + $snip2Content = (Get-SnipContent $sqlFile2Content "/*StartSnip*/" "/*EndSnip*/"); + + $FinalContent = (($templateContent.Replace("/*snip1content*/",$snip1Content).Replace("/*snip2content*/",$snip2Content)) -join [System.Environment]::NewLine); + Set-Content -Path (Join-Path $tempPath 'GetFriendlySQLServerVersion.sql') -Value $FinalContent; + + Log-Output("Creating CreateBuildLog.sql...") + $testUtilContent = Get-Content -path (Join-Path $buildPath "../TestUtil/tSQLt_testutil.class.sql" | Resolve-Path); + $CreateBuildLogRaw = (Get-SnipContent $testUtilContent "/*CreateBuildLogStart*/" "/*CreateBuildLogEnd*/"); + $CreateBuildLog = ($CreateBuildLogRaw -join [System.Environment]::NewLine).Replace("tSQLt_testutil.CreateBuildLog","#CreateBuildLog"); + $CreateBuildLog = ($CreateBuildLog + [System.Environment]::NewLine + "EXEC #CreateBuildLog @TableName='"+'$(BuildLogTableName)'+"';" + [System.Environment]::NewLine); + Set-Content -Path (Join-Path $tempPath 'CreateBuildLog.sql') -Value ($CreateBuildLog); + + Log-Output("Packaging tSQLt...") + $toBeZipped = @("ReleaseNotes.txt", "License.txt", "tSQLt.class.sql", "Example.sql", "PrepareServer.sql"); + $compress = @{ + CompressionLevel = "Optimal" + DestinationPath = (Join-Path $OutputPath "tSQLtFiles.zip") + } + Get-ChildItem -Path (Join-Path $tempPath "*") -Include $toBeZipped | Compress-Archive @compress + + # $toBeCopied = @("Version.txt", "tSQLt.class.sql", "CommitId.txt", "GetFriendlySQLServerVersion.sql", "CreateBuildLog.sql"); + $toBeCopied = @("Version.txt", "CommitId.txt", "tSQLt.Private_GetAssemblyKeyBytes.sql", "GetFriendlySQLServerVersion.sql", "CreateBuildLog.sql"); + $toBeCopied | ForEach-Object{(Join-Path $TempPath $_ | Resolve-Path )| Copy-Item -Destination $OutputPath;} + # Copy-Item (Join-Path $tempPath "ReleaseNotes.txt" | Resolve-Path) -Destination (Join-Path $outputPath "ReadMe.txt"); + + Log-Output("Creating tSQLt Snippets...") + & ./tSQLt_Build/PackagetSQLtSnippets.ps1 + +} +catch{ + throw +} +finally{ + Pop-Location +} \ No newline at end of file diff --git a/Build/tSQLt_Build/ConcatenateFiles.ps1 b/Build/tSQLt_Build/ConcatenateFiles.ps1 new file mode 100644 index 000000000..1375262b6 --- /dev/null +++ b/Build/tSQLt_Build/ConcatenateFiles.ps1 @@ -0,0 +1,125 @@ +param( + [Parameter(Mandatory=$true)][string]$OutputFile, + [Parameter(Mandatory=$false)][string]$SeparatorTemplate = $null, + [Parameter(Mandatory=$false)][string[]]$SeparatorContent = $null, + [Parameter(Mandatory=$true, ValueFromPipeline)][AllowNull()]$InputPath, + [array]$replacements = @{}, + [string]$IncludePattern, + [string]$Bracket = '', + [switch]$IncludeFromStart +) + +function Get-FileContent { + param( + [string]$filePath, + [string]$bracket, + [bool]$includeFromStart, + [string[]]$separator + ) + + $include = $includeFromStart + $includeHeader = $true + $includeNameInHeader = $true + $content = @() + # Write-Host(">$bracket-n<") + + Get-Content $filePath -ErrorAction Stop| ForEach-Object { + # Write-Host("$Include::>$_<") + if ($bracket -ne '' -and $_ -eq "$bracket-h") { + $includeHeader = $false + } elseif ($bracket -ne '' -and $_ -eq "$bracket-n") { + $includeNameInHeader = $false + } elseif ($bracket -ne '' -and $_ -eq "$bracket+") { + $include = $true + } elseif ($bracket -ne '' -and $_ -eq "$bracket-") { + $include = $false + } elseif ($include) { + $content += $_ + } + } + if($includeHeader){ + $filename = "" + if($includeNameInHeader){ + $fileName = (Split-Path $file -Leaf) + } + $content = ($separator|ForEach-Object{$_.Replace("/*--FILENAME--*/",$fileName)}) + $content + } + return $content +} + +# Function to concatenate files +function Concatenate-Files { + param( + [object]$fileIterator, + [string[]]$separator, + [string]$bracket, + [bool]$includeFromStart + ) + + $output = @() + foreach ($file in $fileIterator) { + Write-Verbose("-->$file") + $fileContent = Get-FileContent -filePath $file -bracket $bracket -includeFromStart $includeFromStart -separator $separator + $output += $fileContent + } + + return $output +} + +Write-Verbose("OutputFile: $OutputFile") +Write-Verbose("SeparatorTemplate: >$SeparatorTemplate<") +Write-Verbose("Input: $InputPath") + +if([string]::IsNullOrWhiteSpace($SeparatorTemplate)){ + if($null -eq $SeparatorContent){ + $SeparatorContent = @(); + } +} +else{ + $SeparatorContent = Get-Content $SeparatorTemplate -ErrorAction Stop +} +Write-Verbose(">--Separator Template-->") +$SeparatorContent|%{Write-Verbose(">:$_")} +Write-Verbose("<--Separator Template--<") +if($Bracket -eq ''){ + $IncludeFromStart = $true; +} + +try{ + + if($null -eq $InputPath){ + Write-Verbose("scriptPath: ") + $fileIterator = @() + } + elseif($InputPath -is [System.Collections.IEnumerable]){ + Write-Verbose("scriptPath: /") + $fileIterator = $InputPath + } + elseif (Test-Path $InputPath -PathType Container) { + Write-Verbose("scriptPath: $InputPath") + $fileIterator = Get-ChildItem $InputPath -Filter $IncludePattern + } + else { + $scriptPath = (Split-Path $InputPath) + Write-Verbose("scriptPath: $scriptPath") + $fileList = Get-Content $InputPath -ErrorAction Stop + $fileIterator = $fileList | ForEach-Object { Join-Path $scriptPath $_ | Resolve-Path} + } + + $concatenatedContent = (Concatenate-Files -fileIterator $fileIterator -separator $SeparatorContent -bracket $Bracket -includeFromStart $IncludeFromStart) -join "`r`n" + $replacements|ForEach-Object{ + $sv = $_["s"] + $rv=$_["r"]; + $isRegex = $_.ContainsKey("isRegex") -and $_["isRegex"]; + Write-Verbose("Replacing >$sv< with >$rv< [regex:$isRegex]..."); + if($isRegex){ + $concatenatedContent = $concatenatedContent -replace $sv, $rv + }else{ + $concatenatedContent = $concatenatedContent.Replace($sv, $rv) + } + } + $concatenatedContent | Out-File $OutputFile +}catch{ + throw +} +# $concatenatedContent \ No newline at end of file diff --git a/Build/tSQLt_Build/CreateAssemblyGenerator.ps1 b/Build/tSQLt_Build/CreateAssemblyGenerator.ps1 new file mode 100644 index 000000000..ed5eb9caf --- /dev/null +++ b/Build/tSQLt_Build/CreateAssemblyGenerator.ps1 @@ -0,0 +1,95 @@ +using module "../CommonFunctionsAndMethods.psm1"; + +param ( + [string]$OutputPath, + [string]$DllPath, + [string]$TemplatePath, + [string]$HexPlaceholder, + [string]$ThumbprintPlaceholder, + [int]$MaxLineLength, + [string]$LineSeparator +) + +function Convert-DllToHex { + param ( + [byte[]]$Bytes, + [int]$MaxLineLength, + [string]$LineSeparator + ) + + # Convert to hex string + $hexString = ($Bytes | ForEach-Object { $_.ToString("X2") }) -join '' + + # Split hex string into lines of specified length + $hexLines = $hexString -split "(.{$MaxLineLength})" | Where-Object { $_ } + + # Join lines with separator + return ($hexLines -join $LineSeparator) +} + +function Get-AssemblyThumbprint { + param ( + [string]$DllPath + ) + + $getAssemblyInfoPath = (Join-Path $PSScriptRoot "GetAssemblyInfo.ps1" | Resolve-Path) + $token = & $getAssemblyInfoPath $DllPath -t + return $token +} + +function Replace-TemplatePlaceholders { + param ( + [string]$TemplatePath, + [string]$HexPlaceholder, + [string]$ThumbprintPlaceholder, + [string]$HexContent, + [string]$Thumbprint + ) + + $templateContent = Get-Content $TemplatePath -Raw + + # Replace placeholders + $templateContent = $templateContent -replace $ThumbprintPlaceholder, ('--TTTT--' + $ThumbprintPlaceholder + '--TTTT--') + $templateContent = $templateContent -replace $HexPlaceholder, ('0x' + $HexContent) + $templateContent = $templateContent -replace ('--TTTT--' + $ThumbprintPlaceholder + '--TTTT--'), ('0x' + $Thumbprint) + + return $templateContent +} + +if (-not (Test-Path $DllPath)) { + Write-Error "DLL not found at path: $DllPath" + return +} + +if (-not (Test-Path $TemplatePath)) { + Write-Error "Template file not found at path: $TemplatePath" + return +} + +if ($MaxLineLength -le 10) { + Write-Error "Max line length must be greater than 10" + return +} + +try { + # Read DLL file bytes + $bytes = [System.IO.File]::ReadAllBytes($DllPath) + + # Convert DLL to hex + $hexContent = Convert-DllToHex -Bytes $bytes -MaxLineLength $MaxLineLength -LineSeparator $LineSeparator + + # Get assembly thumbprint + $thumbprint = Get-AssemblyThumbprint -DllPath $DllPath + + # Replace placeholders in template + $outputContent = Replace-TemplatePlaceholders -TemplatePath $TemplatePath -HexPlaceholder $HexPlaceholder -ThumbprintPlaceholder $ThumbprintPlaceholder -HexContent $hexContent -Thumbprint $thumbprint + + # # Output to file + $outputContent | Out-File -FilePath $outputPath + + Log-Output("Output generated: $outputPath"); + # $outputContent +} +catch { + Write-Error "Error: $_" +} diff --git a/Build/tSQLt_Build/CreateDropClassStatement.ps1 b/Build/tSQLt_Build/CreateDropClassStatement.ps1 new file mode 100644 index 000000000..b57787b9c --- /dev/null +++ b/Build/tSQLt_Build/CreateDropClassStatement.ps1 @@ -0,0 +1,53 @@ +using module "../CommonFunctionsAndMethods.psm1"; +Push-Location -Path $PSScriptRoot; +try{ + $baseDir = '..'; + $buildPath = $baseDir +'/'; + $tempPath = $baseDir + '/temp/tSQLtBuild/'; + $sourcePath = $baseDir + '/../Source/'; + + Log-Output '' + + $DropClassFileContent = Get-Content -path ($sourcePath+"tSQLt.DropClass.ssp.sql"); + $GetDropItemCmdFileContent = Get-Content -path ($sourcePath+"tSQLt.Private_GetDropItemCmd.sfn.sql"); + $TemplateFileContent = Get-Content -path ($sourcePath+"tSQLt.TempDropStatement.mdl.sql"); + + $OutputFilePath = $tempPath+"TempDropClass.sql"; + + $DropClassSnip = ($DropClassFileContent | Get-SnipContent -startSnipPattern "/*SnipStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipEnd: CreateDropClassStatement.ps1*/"); + $DropItemSnip = ($GetDropItemCmdFileContent | Get-SnipContent -startSnipPattern "/*SnipStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipEnd: CreateDropClassStatement.ps1*/"); + $DropItemParamSnip = ($GetDropItemCmdFileContent | Get-SnipContent -startSnipPattern "/*SnipParamStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipParamEnd: CreateDropClassStatement.ps1*/"); + + $VariablesString = ($DropItemParamSnip.trim() -join ' ') + + $VariableNames = (Select-String '@\S+' -input $VariablesString -AllMatches|ForEach-Object{$_.matches.Value}); + + $DISP1 = ($DropItemSnip | ForEach-Object{ + $s=$_; + for($i = 0;$i -lt $VariableNames.count;$i++){ + $s=$s -replace $VariableNames[$i], ("($"+($i+1)+")") + }; + $s; + }); + $DISP2 = $DISP1.trim() -join ' '; + + $DropItemSnipPrepared = "("+ $DISP2 + ")"; + $RawDropClassStatement = $DropClassSnip -replace 'tSQLt.Private_GetDropItemCmd\s*\(\s*([^,]*)\s*,\s*([^)]*)\s*\)',$DropItemSnipPrepared; + + $DropClassStatement = ($RawDropClassStatement.trim()|Where-Object {$_ -ne "" -and $_ -notmatch "^GO(\s.*)?"}) -join ' '; + Log-Output("OutputFilePath:$OutputFilePath") + + Set-Content -Path $OutputFilePath -Value ($TemplateFileContent.Replace("/*--DROPSTATEMENT--*/",$DropClassStatement)); + + Log-Output '' +} +finally{ + Pop-Location; +} + + <# TODO + --> this makes tSQLt.class.sql truely re-runnable + --> Test this: Empty File TempDropClass.sql file should throw an error + --> Test this: If the $tempPath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? + --> Test this: If the $sourcePath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? + #> diff --git a/Build/tSQLt_Build/GetAssemblyInfo.ps1 b/Build/tSQLt_Build/GetAssemblyInfo.ps1 new file mode 100644 index 000000000..04e031abf --- /dev/null +++ b/Build/tSQLt_Build/GetAssemblyInfo.ps1 @@ -0,0 +1,49 @@ +param( + [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] + [string]$dllPath, + [Parameter(Mandatory=$true, ParameterSetName="Version")] + [switch]$v, + [Parameter(Mandatory=$true, ParameterSetName="Key")] + [switch]$k, + [Parameter(Mandatory=$true, ParameterSetName="Token")] + [switch]$t +) + +function Get-AssemblyInfo { + param( + [string]$dllPath, + [switch]$v, + [switch]$k, + [switch]$t + ) + + try { + $assemblyName = [System.Reflection.AssemblyName]::GetAssemblyName($dllPath) + + if($v) { + return $assemblyName.Version.ToString() + } + elseif($k){ + $publicKey = $assemblyName.GetPublicKey() + if ($null -eq $publicKey -or $publicKey.Length -eq 0) { + return "" + } else { + return [BitConverter]::ToString($publicKey) -replace '-' + } + } + elseif($t){ + $publicKeyToken = $assemblyName.GetPublicKeyToken() + if ($null -eq $publicKeyToken -or $publicKeyToken.Length -eq 0) { + return "" + } else { + return [BitConverter]::ToString($publicKeyToken) -replace '-' + } + } + } catch { + Write-Error "Error processing assembly: $_" + exit 1 + } +} + +(Get-AssemblyInfo -dllPath $dllPath -v:$v -k:$k -t:$t) + diff --git a/Build/tSQLt_Build/PackagetSQLtSnippets.ps1 b/Build/tSQLt_Build/PackagetSQLtSnippets.ps1 new file mode 100644 index 000000000..f1252220c --- /dev/null +++ b/Build/tSQLt_Build/PackagetSQLtSnippets.ps1 @@ -0,0 +1,25 @@ +using module "../CommonFunctionsAndMethods.psm1"; + +$__=$__ +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +try{ + $snippetsPath = Join-Path $invocationDir '../../Snippets' | Resolve-Path; + $outputPath = Join-Path $invocationDir '../output/tSQLtBuild' |Resolve-Path; + $tempPath = Join-Path $invocationDir '../temp/tSQLtBuild/SQLPromptSnippets'; + Remove-DirectoryQuietly -Path $tempPath; + $__ = New-Item -ItemType "directory" -Path $tempPath; + + + $fileList = Get-ChildItem -path (Join-Path $snippetsPath '*') -include "*.sqlpromptsnippet","ReadMe.txt" + $fileList | Copy-Item -Destination $tempPath + + $compress = @{ + CompressionLevel = "Optimal" + DestinationPath = (Join-Path $outputPath "tSQLtSnippets(SQLPrompt).zip") + } + Get-ChildItem -Path (Join-Path $tempPath "*") | Compress-Archive @compress +} +finally{ + Pop-Location +} \ No newline at end of file diff --git a/Build/tSQLt_BuildCLR.ps1 b/Build/tSQLt_BuildCLR.ps1 new file mode 100644 index 000000000..7acc1733a --- /dev/null +++ b/Build/tSQLt_BuildCLR.ps1 @@ -0,0 +1,34 @@ +using module "./CommonFunctionsAndMethods.psm1"; +param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $pfxFilePath , + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][securestring] $pfxPassword +) +Push-Location -Path $PSScriptRoot +Write-Verbose((Get-Location).Path) +try{ + $OutputPath = "./output/tSQLtCLR/"; + $TempPath = "./temp/tSQLtCLR/"; + + <# Clean #> + Remove-DirectoryQuietly -Path $TempPath; + Remove-DirectoryQuietly -Path $OutputPath; + Get-ChildItem -Path ("../tSQLtCLR/") -Recurse -Include bin, obj|Foreach-Object{Remove-DirectoryQuietly -Path $_} + + <# Init directories, capturing the return values in a variable so that they don't print. #> + $__=$__; + $__ = New-Item -ItemType "directory" -Path $TempPath; + $__ = New-Item -ItemType "directory" -Path $OutputPath; + + ../tSQLtCLR/Build.ps1 -pfxFilePath $pfxFilePath -pfxPassword $pfxPassword + + Get-ChildItem -Path ("../tSQLtCLR/*/bin") -Recurse -Include *.dll | Copy-Item -Destination $TempPath; + + $compress = @{ + CompressionLevel = "Optimal" + DestinationPath = $OutputPath + "/tSQLtCLR.zip" + } + Get-ChildItem -Path $TempPath | Compress-Archive @compress +} +finally{ + Pop-Location +} \ No newline at end of file diff --git a/Build/tSQLt_BuildDacpac.ps1 b/Build/tSQLt_BuildDacpac.ps1 new file mode 100644 index 000000000..b23e6daa1 --- /dev/null +++ b/Build/tSQLt_BuildDacpac.ps1 @@ -0,0 +1,59 @@ +# using module "./CommonFunctionsAndMethods.psm1"; + +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][SqlServerConnection] $SqlServerConnection, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $DacPacDatabaseName +); +<# +Technically this should be called by a matrixed job, so that dacpacs are built for all versions (we support, like not 2005, 2008) +#> + +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +$cfam = (Join-Path $invocationDir "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Verbose "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force +Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded + +try{ + + $OutputPath = (Join-Path $invocationDir "/output/DacpacBuild/"); + $TempPath = (Join-Path $invocationDir "/temp/DacpacBuild/"); + + <# Clean #> + Remove-DirectoryQuietly -Path $TempPath; + Remove-DirectoryQuietly -Path $OutputPath; + + $__ = New-Item -ItemType "directory" -Path $TempPath; + $__ = New-Item -ItemType "directory" -Path $OutputPath; + + Expand-Archive -Path (Join-Path $invocationDir "/output/tSQLtBuild/tSQLtFiles.zip"|Resolve-Path) -DestinationPath $TempPath -Force; + Expand-Archive -Path (Join-Path $invocationDir "/output/tSQLtTests/tSQLt.tests.zip"|Resolve-Path) -DestinationPath $TempPath -Force; + + Set-Location $TempPath; + Log-Output('Building Database') + Log-Output('-- Executing ResetValidationServer.sql') + Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames @('ResetValidationServer.sql'); + Log-Output('-- Executing PrepareServer.sql') + Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames 'PrepareServer.sql'; + Log-Output('-- Executing CreateBuildDb.sql') + Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames "CreateBuildDb.sql" -Database "tempdb" -AdditionalParameters @{NewDbName=$DacPacDatabaseName} -PrintSqlOutput $true; + Log-Output('-- Executing tSQLt.class.sql') + Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames "tSQLt.class.sql" -Database "$DacPacDatabaseName"; + Log-Output('Building DACPAC') + $FriendlySQLServerVersion = Get-FriendlySQLServerVersion -SqlServerConnection $SqlServerConnection; + $tSQLtDacpacFileName = "tSQLt."+$FriendlySQLServerVersion+".dacpac"; + $tSQLtApplicationName = "tSQLt."+$FriendlySQLServerVersion; + $tSQLtConnectionString = $SqlServerConnection.GetConnectionString($DacPacDatabaseName,"tSQLt_BuildDacpac") + & sqlpackage --roll-forward Major /a:Extract /scs:"$tSQLtConnectionString" /tf:"$tSQLtDacpacFileName" /p:DacApplicationName="$tSQLtApplicationName" /p:IgnoreExtendedProperties=true /p:DacMajorVersion=0 /p:DacMinorVersion=1 /p:ExtractUsageProperties=false + if($LASTEXITCODE -ne 0) { + throw "error during execution of dacpac " + $tSQLtDacpacFileName; + } + + Copy-Item -Path $tSQLtDacpacFileName -Destination $OutputPath; + +} +finally{ + Pop-Location +} \ No newline at end of file diff --git a/Build/tSQLt_BuildPackage.ps1 b/Build/tSQLt_BuildPackage.ps1 new file mode 100644 index 000000000..9f2392fab --- /dev/null +++ b/Build/tSQLt_BuildPackage.ps1 @@ -0,0 +1,96 @@ +using module "./CommonFunctionsAndMethods.psm1"; + +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +try{ + + $tSQLtBuildPath = (Join-Path $invocationDir '/output/tSQLtBuild/'|Resolve-Path); + $tSQLtTestsPath = (Join-Path $invocationDir '/output/tSQLtTests/'|Resolve-Path); + $DacpacBuildPath = (Join-Path $invocationDir '/output/DacpacBuild/'|Resolve-Path); + # Write-Warning "BuildPackage Inputs:" + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $tSQLtBuildPath -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $tSQLtTestsPath -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $DacpacBuildPath -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + + $OutputPath = (Join-Path $invocationDir "/output/tSQLt/"); + $TempPath = (Join-Path $invocationDir "/temp/tSQLt/"); + + $PublicOutputPath = (Join-Path $OutputPath "public/"); + $PublicTempPath = (Join-Path $TempPath "public/"); + $ValidationOutputPath = (Join-Path $OutputPath "validation/"); + $tSQLtDacpacPath = (Join-Path $PublicTempPath "tSQLtDacpacs/"); + $SourcePath = (Join-Path $TempPath "Source/"); + $DacpacSourcePath = (Join-Path $SourcePath "Dacpacs/"); + + $PublicOutputFiles = @( + ($PublicTempPath + "/ReadMe.txt"), + ($SourcePath + "/tSQLtSnippets(SQLPrompt).zip") + ); + $ValidationOutputFiles = @( + ($SourcePath + "/Version.txt"), + ($SourcePath + "/CommitId.txt"), + ($SourcePath + "/tSQLt.tests.zip"), + ($SourcePath + "/CreateBuildLog.sql"), + ($SourcePath + "/GetFriendlySQLServerVersion.sql") + ); + + <# Clean #> + Remove-DirectoryQuietly -Path $TempPath; + Remove-DirectoryQuietly -Path $OutputPath; + + <# Init directories, capturing the return values in a variable so that they don't print. #> + $__ = New-Item -ItemType "directory" -Path $PublicOutputPath; + $__ = New-Item -ItemType "directory" -Path $tSQLtDacpacPath; + $__ = New-Item -ItemType "directory" -Path $ValidationOutputPath; + $__ = New-Item -ItemType "directory" -Path $SourcePath; + $__ = New-Item -ItemType "directory" -Path $DacpacSourcePath; + + Log-Output("Copying source files...") + $files = @( + "$($tSQLtBuildPath)Version.txt", + "$($tSQLtBuildPath)CommitId.txt", + "$($tSQLtBuildPath)CreateBuildLog.sql", + "$($tSQLtBuildPath)GetFriendlySQLServerVersion.sql", + "$($tSQLtTestsPath)tSQLt.tests.zip", + "$($tSQLtBuildPath)tSQLtSnippets(SQLPrompt).zip", + "$($tSQLtBuildPath)tSQLtFiles.zip" + ); + $files | ForEach-Object{$_ | Copy-Item -Destination $SourcePath} + Get-ChildItem $DacpacBuildPath | Copy-Item -Destination $DacpacSourcePath + + <# Copy files to temp path #> + Expand-Archive -Path (Join-Path $SourcePath "tSQLtFiles.zip" | Resolve-Path) -DestinationPath $PublicTempPath; + # Get-ChildItem -Path ($dir + "/output/DacpacBuild/tSQLtFacade.*.dacpac") | Copy-Item -Destination $FacadeDacpacPath; + Get-ChildItem -Path $DacpacSourcePath -Filter 'tSQLt.*.dacpac' | Copy-Item -Destination $tSQLtDacpacPath; + + Copy-Item (Join-Path $PublicTempPath "ReleaseNotes.txt" | Resolve-Path) -Destination (Join-Path $PublicTempPath "ReadMe.txt"); + + # Write-Warning "BuildPackage Pre-Zip:" + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $PublicTempPath -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $ValidationOutputFiles -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + + <# Create the tSQLt.zip in the public output path #> + $compress = @{ + CompressionLevel = "Optimal" + DestinationPath = $PublicOutputPath + "/tSQLt.zip" + } + Get-ChildItem -Path $PublicTempPath | Compress-Archive @compress + + <# Copy all public files into public output path #> + Copy-Item -Path $PublicOutputFiles -Destination $PublicOutputPath + + <# Copy all validation files into validation output path #> + Copy-Item -Path $ValidationOutputFiles -Destination $ValidationOutputPath + +} +finally{ + Pop-Location +} diff --git a/Build/tSQLt_BuildTests.ps1 b/Build/tSQLt_BuildTests.ps1 new file mode 100644 index 000000000..d7dbbea51 --- /dev/null +++ b/Build/tSQLt_BuildTests.ps1 @@ -0,0 +1,198 @@ +using module "./CommonFunctionsAndMethods.psm1"; + +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +try{ + + $OutputPath = (Join-Path $invocationDir "./output/tSQLtTests/"); + $TempPath = (Join-Path $invocationDir "./temp/tSQLtTests/"); + $TempCLRPath = (Join-Path $TempPath "/tSQLtCLR/"); + $PackagePath = (Join-Path $TempPath "Package/"); + $SourcePath = (Join-Path $TempPath "Source/"); + + <# Clean #> + Remove-DirectoryQuietly -Path $TempPath; + Remove-DirectoryQuietly -Path $OutputPath; + <# Init directories, capturing the return values in a variable so that they don't print. #> + $__ = $__ + $__ = New-Item -ItemType "directory" -Path $TempPath; + $__ = New-Item -ItemType "directory" -Path $SourcePath; + $__ = New-Item -ItemType "directory" -Path $PackagePath; + $__ = New-Item -ItemType "directory" -Path $OutputPath; + + Log-Output("Copying source files...") + $files = @( + "output/tSQLtCLR/tSQLtCLR.zip", + "output/tSQLtBuild/tSQLt.Private_GetAssemblyKeyBytes.sql" + ); + $files|%{(Join-Path $invocationDir $_ | Resolve-Path) | Copy-Item -Destination $SourcePath} + + Log-Output("Unzipping tSQLtCLR DLLs...") + Expand-Archive (Join-Path $SourcePath "tSQLtCLR.zip") -DestinationPath $TempCLRPath + + Log-Output("Generating CREATE ASSEMBLY statement for tSQLtTestUtilCLR...") + $tSQLtTestUtilCLRDLLPath = (Join-Path $TempCLRPath "tSQLtTestUtilCLR.dll") | Resolve-Path + $tSQLtTestUtilCLRSQLPath = (Join-Path (Get-Location) "../TestUtil/tSQLtTestUtilCLR.mdl.sql") | Resolve-Path + $tSQLtTestUtilCLROutputPath = (Join-Path $TempPath "CreateTestUtilAssembly.sql") + ./tSQLt_Build/CreateAssemblyGenerator.ps1 $tSQLtTestUtilCLROutputPath $tSQLtTestUtilCLRDLLPath $tSQLtTestUtilCLRSQLPath 0x000000 "0xZZZZZ" 20000 "+`n0x" + + Log-Output("Generating CREATE ASSEMBLY statement for UnsignedEmpty...") + $UnsignedEmptyCLRDLLPath = (Join-Path $TempCLRPath "UnsignedEmpty.dll") | Resolve-Path + $UnsignedEmptyCLRSQLPath = (Join-Path (Get-Location) "../TestUtil/GetUnsignedEmptyBytes.mdl.sql") | Resolve-Path + $UnsignedEmptyCLROutputPath = (Join-Path $TempPath "GetUnsignedEmptyBytes.sql") + ./tSQLt_Build/CreateAssemblyGenerator.ps1 $UnsignedEmptyCLROutputPath $UnsignedEmptyCLRDLLPath $UnsignedEmptyCLRSQLPath 0x000000 "0xZZZZZ" 20000 "+`n0x" + + Log-Output("Building TestUtil.sql file...") + $tSQLtSeparatorPath = (Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path + $tSQLtFileListPath = (Join-Path (Get-Location) "../TestUtil/BuildOrder.txt") | Resolve-Path + $tSQLtClassOutputPath = (Join-Path $PackagePath "TestUtil.sql") + $replacements = @( + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;} + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ); + ./tSQLt_Build/ConcatenateFiles.ps1 -OutputFile $tSQLtClassOutputPath -SeparatorTemplate $tSQLtSeparatorPath -InputPath $tSQLtFileListPath -Replacements $replacements + + Log-Output("Building TestUtilTests.sql file...") + $parameters = @{ + OutputFile= (Join-Path $PackagePath "TestUtilTests.sql") + SeparatorTemplate= ((Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path) + InputPath= ((Join-Path (Get-Location) "../TestUtilTests") | Resolve-Path) + IncludePattern= "tSQLt_testutil*test.class.sql" + Replacements= @( + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;} + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ) + } + ./tSQLt_Build/ConcatenateFiles.ps1 @parameters + + Log-Output("Building TestUtilTests.SA.sql file...") + $parameters = @{ + OutputFile= (Join-Path $PackagePath "TestUtilTests.SA.sql") + SeparatorTemplate= ((Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path) + InputPath= ((Join-Path (Get-Location) "../TestUtilTests") | Resolve-Path) + IncludePattern= "tSQLt_testutil*test_SA.class.sql" + Replacements= @( + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;} + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ) + } + ./tSQLt_Build/ConcatenateFiles.ps1 @parameters + + Log-Output("Building AllTests.sql file...") + $parameters = @{ + OutputFile= (Join-Path $PackagePath "AllTests.sql") + SeparatorTemplate= ((Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path) + InputPath= ((Join-Path (Get-Location) "../Tests") | Resolve-Path) + IncludePattern= "*.class.sql" + Replacements= @( + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;} + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ) + } + ./tSQLt_Build/ConcatenateFiles.ps1 @parameters + + Log-Output("Building AllTests.EXTERNAL_ACCESS.sql file...") + $parameters = @{ + OutputFile= (Join-Path $PackagePath "AllTests.EXTERNAL_ACCESS.sql") + SeparatorTemplate= ((Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path) + InputPath= ((Join-Path (Get-Location) "../Tests.EXTERNAL_ACCESS") | Resolve-Path) + IncludePattern= "*.class.sql" + Replacements= @( + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;} + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ) + } + ./tSQLt_Build/ConcatenateFiles.ps1 @parameters + + Log-Output("Building AllTests.EXTERNAL_ACCESS_KEY_EXISTS.sql file...") + $parameters = @{ + OutputFile= (Join-Path $PackagePath "AllTests.EXTERNAL_ACCESS_KEY_EXISTS.sql") + SeparatorTemplate= ((Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path) + InputPath= ((Join-Path (Get-Location) "../Tests.EXTERNAL_ACCESS_KEY_EXISTS") | Resolve-Path) + IncludePattern= "*.class.sql" + Replacements= @( + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;} + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ) + } + ./tSQLt_Build/ConcatenateFiles.ps1 @parameters + + Log-Output("Building AllTests.SA.sql file...") + $parameters = @{ + OutputFile= (Join-Path $PackagePath "AllTests.SA.sql") + SeparatorTemplate= ((Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path) + InputPath= ((Join-Path (Get-Location) "../Tests.SA") | Resolve-Path) + IncludePattern= "*.class.sql" + Replacements= @( + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;} + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ) + } + ./tSQLt_Build/ConcatenateFiles.ps1 @parameters + + Log-Output("Copying misc files...") + $files = @( + "../../Tests/BootStrapTest.sql", + "ExecuteAs(tSQLt.Build).sql", + "ExecuteAs(tSQLt.Build.SA).sql", + "ExecuteAsCleanup.sql" + "Drop(tSQLtAssemblyKey)(Pre2017).sql", + "Install(tSQLtAssemblyKey).sql", + "ChangeDbAndExecuteStatement(tSQLt.Build).sql", + "EnableExternalAccess.sql", + "DisableExternalAccess.sql", + "Drop(master.tSQLt_testutil).sql", + "Install(master.tSQLt_testutil).sql", + "GetFailedTestCount.sql", + "Add(tSQLt.Built)ToExampleDB.sql", + "UseTempDb.sql", + "../../Examples/TestThatExamplesAreDeployed.sql" + ); + $files|%{(Join-Path $invocationDir 'SQL' $_ | Resolve-Path) | Copy-Item -Destination $PackagePath} + + Log-Output("Building CreateBuildDb.sql file...") + $parameters = @{ + OutputFile= (Join-Path $PackagePath "CreateBuildDb.sql") + SeparatorTemplate= ((Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path) + InputPath= ((Join-Path (Get-Location) "./SQL/CreateBuildDbBuildOrder.txt") | Resolve-Path) + Replacements= @( + ) + } + ./tSQLt_Build/ConcatenateFiles.ps1 @parameters + + Log-Output("Building ResetValidationServer.sql file...") + $parameters = @{ + OutputFile= (Join-Path $TempPath "ResetValidationServer.tmp.sql") + SeparatorTemplate= ((Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path) + InputPath= ((Join-Path (Get-Location) "../Source/ResetValidationServerBuildOrder1.txt") | Resolve-Path) + Replacements= @( + @{'s'='tSQLt.';'r'='#';} + @{'s'="OBJECT_ID('#";'r'="OBJECT_ID('tempdb..#";} + ) + } + ./tSQLt_Build/ConcatenateFiles.ps1 @parameters + $parameters = @{ + OutputFile= (Join-Path $PackagePath "ResetValidationServer.sql") + SeparatorTemplate= ((Join-Path (Get-Location) "./SQL/SeparatorTemplate.sql") | Resolve-Path) + InputPath= ((Join-Path (Get-Location) "../Source/ResetValidationServerBuildOrder2.txt") | Resolve-Path) + Replacements= @( + @{'s'='---Build-';'r'='';} + @{'s'='---Build+';'r'='';} + @{'s'="(?m)^\s*--.*";'r'='';isRegex=$true;} + @{'s'='(?m)^(?:[\t ]*(?:\r?\n|\r))+';'r'='';isRegex=$true;} + @{'s'='(?m)^\s*GO\s*((\r?\n)\s*GO\s*)+$';'r'='GO';isRegex=$true;} + ) + } + ./tSQLt_Build/ConcatenateFiles.ps1 @parameters + + Log-Output("Packaging Test Files...") + $compress = @{ + CompressionLevel = "Optimal" + DestinationPath = (Join-Path $outputPath "tSQLt.tests.zip") + } + Get-ChildItem -Path (Join-Path $PackagePath "*") | Compress-Archive @compress + +} +finally{ + Pop-Location +} \ No newline at end of file diff --git a/Build/tSQLt_Validate.ps1 b/Build/tSQLt_Validate.ps1 new file mode 100644 index 000000000..22ff1d02b --- /dev/null +++ b/Build/tSQLt_Validate.ps1 @@ -0,0 +1,236 @@ +using module "./CommonFunctionsAndMethods.psm1"; + +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][SqlServerConnection] $SqlServerConnection, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $MainTestDb, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $DacpacTestDb, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ExampleTestDb, + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $LogTableName = "" +); + + +$validateStartTime = Get-Date; + +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +try{ + + if([string]::IsNullOrWhiteSpace($LogTableName)){ + $LogTableName = "tempdb.dbo.[tSQLt-MultiRunLog(" + (New-Guid) + ")]"; + } + + $OutputPath = (Join-Path $invocationDir "/output/Validate/"); + $TempPath = (Join-Path $invocationDir "/temp/Validate/"); + $tSQLtPath = (Join-Path $TempPath "/tSQLt/"); + $TestsPath = (Join-Path $TempPath "/Tests/"); + $HelperSQLPath = (Join-Path $TempPath "/Tests/"); + $ResultsPath = (Join-Path $TempPath "/Results/"); + + $tSQLtZipPath = (Join-Path $invocationDir "/output/tSQLt/public/tSQLt.zip" |Resolve-Path); + $tSQLtTestsPath = (Join-Path $invocationDir "/output/tSQLt/validation" |Resolve-Path); + + <# Clean #> + Remove-DirectoryQuietly -Path $TempPath; + Remove-DirectoryQuietly -Path $OutputPath; + + <# Init directories, capturing the return values in a variable so that they don't print. #> + $__ = New-Item -ItemType "directory" -Path $tSQLtPath; + $__ = New-Item -ItemType "directory" -Path $TestsPath; + $__ = New-Item -ItemType "directory" -Path $ResultsPath; + $__ = New-Item -ItemType "directory" -Path $OutputPath; + + <# Copy files to temp path #> + Expand-Archive -Path ($tSQLtZipPath) -DestinationPath $tSQLtPath; + Expand-Archive -Path (Join-Path $tSQLtTestsPath "tSQLt.tests.zip" | Resolve-Path) -DestinationPath $TestsPath; + @( + 'CreateBuildLog.sql' + 'GetFriendlySQLServerVersion.sql' + 'Version.txt' + 'CommitId.txt' + )|ForEach-Object{(Join-Path $tSQLtTestsPath $_ | Resolve-Path)|Copy-Item -Destination $TestsPath} + + Log-Output('Initialization...') + $tSQLtVersion = (Get-Content -path (Join-Path $TestsPath 'Version.txt' | Resolve-Path)) + $tSQLtCommitId = (Get-Content -path (Join-Path $TestsPath 'CommitId.txt' | Resolve-Path)) + $SQLVersion = Get-FriendlySQLServerVersion -SqlServerConnection $SqlServerConnection -Quiet + + $FixWidth = {param($m); return '| '+$m.PadRight(70,' ')+' |'} + Log-Output(''); + Log-Output("+--------------------------------------------------------------------------+"); + Log-Output($FixWidth.invoke("Validating tSQLt Build")); + Log-Output("+--------------------------------------------------------------------------+"); + Log-Output($FixWidth.invoke("tSQLt Version: $tSQLtVersion")); + Log-Output($FixWidth.invoke("tSQLt CommitId: $tSQLtCommitId")); + Log-Output($FixWidth.invoke("SQL Server: $ServerName")); + Log-Output($FixWidth.invoke("SQL Server Version: $SQLVersion")); + Log-Output($FixWidth.invoke("Log Table Name: $LogTableName")); + Log-Output("+--------------------------------------------------------------------------+"); + Log-Output($FixWidth.invoke("Log Table Name: $LogTableName")); + Log-Output(''); + + Log-Output('Building helper scripts...') + $FixPath={param($l);$l|ForEach-Object{Write-Warning($_); Join-Path $TestsPath $_ | Resolve-Path;}}; + $ConcatenateFiles = {param([string]$output, [string[]]$fileList) + $Parameters = @{ + OutputFile = (Join-Path $TestsPath $output) + InputPath = [string[]](& $FixPath $fileList) + } + & ./tSQLt_Build/ConcatenateFiles.ps1 @Parameters + }; + & $ConcatenateFiles "temp_executeas.sql" @( + "ExecuteAs(tSQLt.Build).sql" + # "ChangeDbAndExecuteStatement(tSQLt.Build).sql" + ) ; + & $ConcatenateFiles "temp_executeas_sa.sql" @( + "ExecuteAs(tSQLt.Build.SA).sql" + # "ChangeDbAndExecuteStatement(tSQLt.Build).sql" + ) ; + & $ConcatenateFiles "temp_executeas_caller.sql" @( + # "ExecuteAs(tSQLt.Build.SA).sql" + # "ChangeDbAndExecuteStatement(tSQLt.Build).sql" + ) ; + & $ConcatenateFiles "temp_executeas_cleanup.sql" @( + "ExecuteAsCleanup.sql" + # "ChangeDbAndExecuteStatement(tSQLt.Build).sql" + ) ; + & $ConcatenateFiles "temp_create_example.sql" @( + "../tSQLt/Example.sql", + "TestUtil.sql", + "Add(tSQLt.Built)ToExampleDB.sql", + "UseTempDb.sql" + ) ; + + Log-Output("Creating Log Table $LogTableName ...") + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Caller = $true + Files = @( + (Join-Path $TestsPath "CreateBuildLog.sql" | Resolve-Path) + ) + DatabaseName = 'tempdb' + AdditionalParameters = @{ + 'BuildLogTableName' = $LogTableName + } + } + Invoke-SQLFileOrQuery @parameters; + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + TestDbName = $MainTestDb + LogTableName = $LogTableName + TestsResultFilePrefix = 'tSQLt' + DeploySource = 'class' + SourcePath = $tSQLtPath + TestsPath = $TestsPath + ResultsPath = $ResultsPath + } + & ./tSQLt_ValidateRunAll.ps1 @parameters + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + TestDbName = $DacpacTestDb + LogTableName = $LogTableName + TestsResultFilePrefix = 'tSQLtDacPac' + DeploySource = 'dacpac' + SourcePath = $tSQLtPath + TestsPath = $TestsPath + ResultsPath = $ResultsPath + } + & ./tSQLt_ValidateRunAll.ps1 @parameters + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + TestDbName = '$null' + LogTableName = $LogTableName + TestsResultFilePrefix = '$null' + DeploySource = 'example' + SourcePath = $tSQLtPath + TestsPath = $TestsPath + ResultsPath = $ResultsPath + } + & ./tSQLt_ValidateRunAll.ps1 @parameters + + Log-Output('Create tSQLt.TestResults.zip ...') + $compress = @{ + CompressionLevel = "Optimal" + DestinationPath = $OutputPath + "/tSQLt.TestResults.zip" + } + Get-ChildItem -Path $ResultsPath | Compress-Archive @compress + + Log-Output('Summary of all tests...') + Write-Warning("+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+"); + Write-Warning("|+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+|"); + Write-Warning("|| ||"); + Write-Warning("|| Build Test Result Summary ||"); + Write-Warning("|| ||"); + Write-Warning("|+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+|"); + Write-Warning("+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+"); + Write-Warning(""); + Write-Warning("Executed on SQL Server Version $SQLVersion"); + Write-Warning(""); + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Query = "SET NOCOUNT ON;EXEC tSQLt_testutil.CheckBuildLog @TableName='$LogTableName';" + DatabaseName = $MainTestDb + PrintSqlOutput = $true + } + Invoke-SQLFileOrQuery @parameters; + + + Log-Output('Confirm All Tests executed ...') + $ExpectedTestResultFiles = ( + 'TestResults_Example.xml', + 'TestResults_tSQLt.xml', + 'TestResults_tSQLt_external_access_key_exists.xml', + 'TestResults_tSQLt_external_access.xml', + 'TestResults_tSQLt_sa.xml', + 'TestResults_tSQLt_TestUtil.xml', + 'TestResults_tSQLt_TestUtil_SA.xml', + 'TestResults_tSQLtDacPac.xml', + 'TestResults_tSQLtDacPac_external_access_key_exists.xml', + 'TestResults_tSQLtDacPac_external_access.xml', + 'TestResults_tSQLtDacPac_sa.xml', + 'TestResults_tSQLtDacPac_TestUtil.xml', + 'TestResults_tSQLtDacPac_TestUtil_SA.xml' + ) + $ActualTestResultFiles = (Get-ChildItem -Path $ResultsPath -Include "TestResults*.xml" -Recurse)|%{$_.name}; + $missingFiles = ($ExpectedTestResultFiles|Where-Object{$ActualTestResultFiles -NotContains $_}) + $superfluousFiles = ($ActualTestResultFiles|Where-Object{$ExpectedTestResultFiles -NotContains $_}) + $matchingFiles = ($ActualTestResultFiles|Where-Object{$ExpectedTestResultFiles -Contains $_}) + Log-Output("+------------------------------------------------"); + Log-Output("| Expected Test Result Files:"); + $matchingFiles|%{Log-Output("| - $_");} + Log-Output("+------------------------------------------------"); + if($missingFiles.length -gt 0){ + Log-Output("| Missing Test Result Files:"); + $missingFiles|%{Log-Output("| - $_");} + }else{ + Log-Output("| No Missing Test Result Files"); + } + Log-Output("+------------------------------------------------"); + if($superfluousFiles.length -gt 0){ + Log-Output("| Unexpected Test Result Files:") + $superfluousFiles|%{Log-Output("| - $_");} + }else{ + Log-Output("| No Unexpected Test Result Files"); + } + Log-Output("+------------------------------------------------"); + if($missingFiles.Length + $superfluousFiles.Length -gt 0){ + Write-Error("Missing or Unexpected Test Result Files!") + } +} +finally{ + Pop-Location + $validateEndTime = Get-Date; + Log-Output("------------------------------------") + Log-Output("Total Duration: $($validateEndTime-$validateStartTime)") + Log-Output("------------------------------------") +} + diff --git a/Build/tSQLt_ValidateRunAll.ps1 b/Build/tSQLt_ValidateRunAll.ps1 new file mode 100644 index 000000000..963cc6553 --- /dev/null +++ b/Build/tSQLt_ValidateRunAll.ps1 @@ -0,0 +1,191 @@ +using module "./CommonFunctionsAndMethods.psm1"; + +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][SqlServerConnection] $SqlServerConnection, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $HelperSQLPath, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $TestDbName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $LogTableName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $TestsResultFilePrefix, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $DeploySource, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SourcePath, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $TestsPath, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ResultsPath +); + + + +function Copy-SQLXmlToFile { + # This has been tested for up to 100MB of test results! + # (Likely there is no limit, other than the 2GB of the XML data type.) + param ( + [SqlServerConnection]$sqlServerConnection, + [string]$DatabaseName, + [string]$query, + [string]$outputFile + ) + + Add-Type -AssemblyName System.Data + + try { + $connection = New-Object System.Data.SqlClient.SqlConnection($sqlServerConnection.GetConnectionString($DatabaseName,"Copy-SQLXmlToFile")) + $connection.Open() + + $command = $connection.CreateCommand() + $command.CommandText = $query + + $dddbefore = Get-Date;Write-Warning("------->>BEFORE<<-------(tSQLt_Validate.ps1:Copy-SQLXmlToFile:ExecuteScalar[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $result = $command.ExecuteScalar() + $dddafter = Get-Date;Write-Warning("------->>After<<-------(tSQLt_Validate.ps1:Copy-SQLXmlToFile:ExecuteScalar[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $dddafter-$dddbefore + [System.IO.File]::WriteAllText($outputFile, $result) + } + catch { + Write-Error "An error occurred: $_" + } + finally { + $connection.Close() + } +} + +Function Invoke-Tests +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][SqlServerConnection] $SqlServerConnection, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $HelperSQLPath, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $DatabaseName, + [Parameter(Mandatory=$false)][switch]$Elevated, + [Parameter(Mandatory=$true)][string] $TestSetName, + [Parameter(Mandatory=$true)][string] $RunCommand, + [Parameter(Mandatory=$true)][string] $OutputFile + ); + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $Elevated + Query = $RunCommand + DatabaseName = $DatabaseName + PrintSqlOutput = $true + } + $dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $parameters; + Invoke-SQLFileOrQuery @parameters; + $dddafter = Get-Date;Write-Verbose("------->>After<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + Write-Verbose("Duration: $($dddafter-$dddbefore)") + + $dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-Tests:Copy-SQLXmlToFile[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + Copy-SQLXmlToFile $SqlServerConnection $DatabaseName "EXEC [tSQLt].[XmlResultFormatter]" $OutputFile + $dddafter = Get-Date;Write-Verbose("------->>After<<-------(tSQLt_Validate.ps1:Invoke-Tests:Copy-SQLXmlToFile[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + Write-Verbose("Duration: $($dddafter-$dddbefore)") + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $DatabaseName + Query = "EXEC tSQLt_testutil.LogMultiRunResult '$TestSetName';" + } + $dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + # $parameters; + Invoke-SQLFileOrQuery @parameters; + $dddafter = Get-Date;Write-Verbose("------->>After<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + Write-Verbose("Duration: $($dddafter-$dddbefore)") + +} +Function Invoke-TestsFromFile +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][SqlServerConnection] $SqlServerConnection, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $HelperSQLPath, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $DatabaseName, + [Parameter(Mandatory=$false)][switch]$Elevated, + [Parameter(Mandatory=$true)][string] $TestFilePath, + [Parameter(Mandatory=$true)][string] $OutputFile + ); + + $TestSetName = [System.IO.Path]::GetFileName($TestFilePath); + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $DatabaseName + Query = "EXEC tSQLt.Reset;" + } + Invoke-SQLFileOrQuery @parameters; + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Files = @($TestFilePath) + DatabaseName = $DatabaseName + } + Invoke-SQLFileOrQuery @parameters; + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $DatabaseName + Elevated = $Elevated + RunCommand = "EXEC tSQLt.SetVerbose @Verbose = 1;EXEC tSQLt.RunNew;" + OutputFile = $OutputFile + TestSetName = $TestSetName + } + Invoke-Tests @parameters; + +} + + + + + + + + +Log-Output('Reset Validation Server...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Caller = $true + Files = @( + (Join-Path $TestsPath "ResetValidationServer.sql" | Resolve-Path) + (Join-Path $SourcePath "PrepareServer.sql" | Resolve-Path) + ) + DatabaseName = 'tempdb' +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output('Run All Tests... prepare master...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "Drop(master.tSQLt_testutil).sql" | Resolve-Path) + (Join-Path $TestsPath "Install(master.tSQLt_testutil).sql" | Resolve-Path) + (Join-Path $TestsPath "Drop(tSQLtAssemblyKey)(Pre2017).sql" | Resolve-Path) + ) + DatabaseName = "tempdb" +} +Invoke-SQLFileOrQuery @parameters; + +#----------------------------------------------------------------------------# +Log-Output('Run All Tests...') +#----------------------------------------------------------------------------# + +if($DeploySource -eq "example"){ + & ./tSQLt_ValidateRunExampleTests.ps1 +}else{ + & ./tSQLt_ValidateRuntSQLtTests.ps1 +} + +Log-Output('Run All Tests... cleanup master...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "Drop(master.tSQLt_testutil).sql" | Resolve-Path) + ) + DatabaseName = "tempdb" +} +Invoke-SQLFileOrQuery @parameters; diff --git a/Build/tSQLt_ValidateRunExampleTests.ps1 b/Build/tSQLt_ValidateRunExampleTests.ps1 new file mode 100644 index 000000000..5cc7cd7ff --- /dev/null +++ b/Build/tSQLt_ValidateRunExampleTests.ps1 @@ -0,0 +1,63 @@ +# +# +# +# +# +# +# + +$TestDbName = 'tSQLt_Example'; + +Log-Output("Run All Tests... Cleanup master ...") +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "Drop(master.tSQLt_testutil).sql" | Resolve-Path) + ) + DatabaseName = "tempdb" + AdditionalParameters = @{NewDbName = $TestDbName} +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output("Run All Tests... Create Database $TestDbName and deploy example code ...") +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "temp_create_example.sql" | Resolve-Path) + ) + DatabaseName = "tempdb" + AdditionalParameters = @{NewDbName = $TestDbName} +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output('Run All Tests... Set SummaryError Off, PrepMultiRun...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Query = "EXEC tSQLt_testutil.PrepMultiRunLogTable;EXEC tSQLt.SetSummaryError @SummaryError=0;" + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output('Run All Tests... TestUtil Tests...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $TestDbName + TestFilePath = (Join-Path $TestsPath "TestThatExamplesAreDeployed.sql") + OutputFile = (Join-Path $ResultsPath "TestResults_Example.xml") +} +Invoke-TestsFromFile @parameters; + +Log-Output('Run All Tests... Set SummaryError O, Capture MultiRun Results...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Query = "EXEC tSQLt.SetSummaryError @SummaryError=1;EXEC tSQLt_testutil.CheckMultiRunResults @noError=1;EXEC tSQLt_testutil.StoreBuildLog @TableName='$LogTableName',@RunGroup='AllTests_Example';" + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; diff --git a/Build/tSQLt_ValidateRuntSQLtTests.ps1 b/Build/tSQLt_ValidateRuntSQLtTests.ps1 new file mode 100644 index 000000000..de54c2288 --- /dev/null +++ b/Build/tSQLt_ValidateRuntSQLtTests.ps1 @@ -0,0 +1,180 @@ +Log-Output("Run All Tests... Create Database $TestDbName ...") +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "CreateBuildDb.sql" | Resolve-Path) + ) + DatabaseName = "tempdb" + AdditionalParameters = @{NewDbName = $TestDbName} +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output('Run All Tests... Install tSQLt...') +if($DeploySource -eq "class"){ + Log-Output('Deploying tSQLt from tSQLt.class.sql...') + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Files = @( + (Join-Path $SourcePath "tSQLt.class.sql" | Resolve-Path) + ) + DatabaseName = $TestDbName + } + Invoke-SQLFileOrQuery @parameters; +}elseif($DeploySource -eq "dacpac"){ + Log-Output('Deploying tSQLt from tSQLt DacPac...') + + $FriendlySQLServerVersion = Get-FriendlySQLServerVersion -SqlServerConnection $SqlServerConnection; + $DacpacFileName = (Join-Path $SourcePath ("tSQLtDacpacs/tSQLt."+$FriendlySQLServerVersion+".dacpac") | Resolve-Path); + $SqlConnectionString = $SqlServerConnection.GetConnectionString($TestDbName,'DeployDacpac') + & sqlpackage /a:Publish /tcs:"$SqlConnectionString" /sf:"$DacpacFileName" + if($LASTEXITCODE -ne 0) { + throw "error during deployment of dacpac " + $DacpacFileName; + } +} + +# Write-Warning('-->>------------>>--') +# Get-FriendlySQLServerVersion -SqlServerConnection $SqlServerConnection +# Write-Warning('--<<------------<<--') +Log-Output('Run All Tests... Disabling External Access...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "DisableExternalAccess.sql" | Resolve-Path) + ) + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output('Run All Tests... Run Bootstrap Tests...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Files = @( + (Join-Path $TestsPath "BootStrapTest.sql" | Resolve-Path) + ) + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output('Run All Tests... Install TestUtil.sql...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Files = @( + (Join-Path $TestsPath "TestUtil.sql" | Resolve-Path) + ) + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output('Run All Tests... Set SummaryError Off, PrepMultiRun...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Query = "EXEC tSQLt_testutil.PrepMultiRunLogTable;EXEC tSQLt.SetSummaryError @SummaryError=0;" + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + + +Log-Output('Run All Tests... TestUtil Tests...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $TestDbName + TestFilePath = (Join-Path $TestsPath "TestUtilTests.sql") + OutputFile = (Join-Path $ResultsPath "TestResults_$TestsResultFilePrefix`_TestUtil.xml") +} +Invoke-TestsFromFile @parameters; + +Log-Output('Run All Tests... TestUtil_SA Tests...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $TestDbName + Elevated = $true + TestFilePath = (Join-Path $TestsPath "TestUtilTests.SA.sql") + OutputFile = (Join-Path $ResultsPath "TestResults_$TestsResultFilePrefix`_TestUtil_SA.xml") +} +Invoke-TestsFromFile @parameters; + +Log-Output('Run All Tests... tSQLt Tests...') + $parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $TestDbName + TestFilePath = (Join-Path $TestsPath "AllTests.sql") + OutputFile = (Join-Path $ResultsPath "TestResults_$TestsResultFilePrefix`.xml") + } + Invoke-TestsFromFile @parameters; + +Log-Output('Run All Tests... tSQLt SA Tests...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $TestDbName + Elevated = $true + TestFilePath = (Join-Path $TestsPath "AllTests.SA.sql") + OutputFile = (Join-Path $ResultsPath "TestResults_$TestsResultFilePrefix`_SA.xml") +} +Invoke-TestsFromFile @parameters; + +Log-Output('Run All Tests... Installing Assembly Key (needed for 2016 only)...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "Install(tSQLtAssemblyKey).sql" | Resolve-Path) + ) + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output('Run All Tests... tSQLt EXTERNAL_ACCESS_KEY_EXISTS Tests...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $TestDbName + Elevated = $true + TestFilePath = (Join-Path $TestsPath "AllTests.EXTERNAL_ACCESS_KEY_EXISTS.sql") + OutputFile = (Join-Path $ResultsPath "TestResults_$TestsResultFilePrefix`_EXTERNAL_ACCESS_KEY_EXISTS.xml") +} +Invoke-TestsFromFile @parameters; + +Log-Output('Run All Tests... Enabling External Access...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "EnableExternalAccess.sql" | Resolve-Path) + ) + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + +Log-Output('Run All Tests... tSQLt EXTERNAL_ACCESS Tests...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + DatabaseName = $TestDbName + Elevated = $true + TestFilePath = (Join-Path $TestsPath "AllTests.EXTERNAL_ACCESS.sql") + OutputFile = (Join-Path $ResultsPath "TestResults_$TestsResultFilePrefix`_EXTERNAL_ACCESS.xml") +} +Invoke-TestsFromFile @parameters; + +Log-Output('Run All Tests... Set SummaryError O, Capture MultiRun Results...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Query = "EXEC tSQLt.SetSummaryError @SummaryError=1;EXEC tSQLt_testutil.CheckMultiRunResults @noError=1;EXEC tSQLt_testutil.StoreBuildLog @TableName='$LogTableName',@RunGroup='AllTests_$TestsResultFilePrefix';" + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; diff --git a/Build_Output/.gitignore b/Build_Output/.gitignore deleted file mode 100644 index 64899dd3a..000000000 --- a/Build_Output/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/*.zip -/*.sql -/*.txt diff --git a/Build_Output/Build_Output.ssmssqlproj b/Build_Output/Build_Output.ssmssqlproj deleted file mode 100644 index 573ffac80..000000000 --- a/Build_Output/Build_Output.ssmssqlproj +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - 2016-01-24T17:37:52.9704757-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - 2016-01-24T19:13:28.8798835-05:00 - SQL - hgad3fnal8.database.windows.net - tSQLt_Azure - SQL - tSQLt_Azure - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AllTests.2008.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AllTests.2012.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AllTests.2014.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AllTests.EXTERNAL_ACCESS.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AllTests.EXTERNAL_ACCESS_KEY_EXISTS.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AllTests.SA.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AllTests.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - BootStrapTest.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ChangeDbAndExecuteStatement(tSQLt.Build).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - CreateBuildDb.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Drop(master.tSQLt_testutil).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Drop(tSQLtExternalAccessKey).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - EnableExternalAccess.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Example.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ExecuteAs(tSQLt.Build).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ExecuteAs(tSQLt.Build.SA).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - GetFailedTestCount.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - GetTestResults.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Install(master.tSQLt_testutil).sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Install(tSQLtExternalAccessKey).sql - - - - - - SetClrEnabled.sql - - - - - - TestThatExamplesAreDeployed.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:hgad3fnal8.database.windows.net:False:tSQLt_Azure - hgad3fnal8.database.windows.net - tSQLt_Azure - TestUtil.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.class.sql - - - - - - - - - - License.txt - - - - - - ReadMe.txt - - - - - - ReleaseNotes.txt - - - - - - tSQLt.tests.zip - - - - - - tSQLt.zip - - - - - - tSQLtSnippets(SQLPrompt).zip - - - - - - Version.txt - - - - - \ No newline at end of file diff --git a/CI/AKS/docker/Dockerfile.mssql2012 b/CI/AKS/docker/Dockerfile.mssql2012 new file mode 100644 index 000000000..2bc7da95c --- /dev/null +++ b/CI/AKS/docker/Dockerfile.mssql2012 @@ -0,0 +1,42 @@ +# Adapted from: https://github.com/Microsoft/mssql-docker/blob/master/windows/mssql-server-windows-developer/dockerfile +# +# +FROM mcr.microsoft.com/windows/servercore:ltsc2019 +LABEL maintainer "Liz B & Sebastian M" + +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Continue'; $ProgressPreference = 'SilentlyContinue';"] + +WORKDIR / + +# Install chocolatey via powershell +ENV chocolateyUseWindowsCompression false +RUN powershell -Command Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) +RUN choco feature disable --name showDownloadProgress + +# MSSQL 2012 Download link +ENV exe "https://download.microsoft.com/download/E/A/B/EABF1E75-54F0-42BB-B0EE-58E837B7A17F/SQLServer2012SP4-KB4018073-x64-ENU.exe" +ENV ACCEPT_EULA="Y" + +# Install MSSQL 2012 +RUN mkdir c:\downloadFiles ; \ + Invoke-WebRequest -Uri $env:exe -OutFile c:\downloadFiles\SQL.exe ; + # mkdir c:\setupFiles ; \ + # Start-Process -Wait -FilePath "c:\downloadFiles\SQL.exe" -ArgumentList /QUIET, /ACTION=Download, /MEDIAPATH="c:\setupFiles"; \ + # $setupExeFullPath = (Get-ChildItem -Path "c:\setupFiles\*.exe").FullName; \ + # $setupExecFullPath; \ + # Start-Process -Wait -FilePath "$setupExeFullPath" -ArgumentList /qs, /x:"c:\setup" ; \ + # c:\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=0 /SQLSVCACCOUNT='NT AUTHORITY\System' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS ; \ + # Remove-Item -Recurse -Force c:\downloadFiles, c:\setupFiles, c:\setup + +# RUN stop-service MSSQLSERVER ; \ +# set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql11.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpdynamicports -value '' ; \ +# set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql11.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpport -value 1433 ; \ +# set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql11.MSSQLSERVER\mssqlserver\' -name LoginMode -value 2 ; +# HEALTHCHECK CMD [ "sqlcmd", "-Q", "select 1" ] + +# COPY start.ps1 / +# RUN net user /add LOCAL_SQLSRVR +# RUN powershell -Command Add-LocalGroupMember -Group "Administrators" -Member "LOCAL_SQLSRVR" +# USER LOCAL_SQLSRVR +# CMD .\start -ACCEPT_EULA $env:ACCEPT_EULA -sqlsrvrlogin "LOCAL_SQLSRVR" -Verbose *> start_script.log +CMD powershell -Command { while($true) { Start-Sleep -Seconds 10 } } diff --git a/CI/AKS/docker/Dockerfile.mssql2014 b/CI/AKS/docker/Dockerfile.mssql2014 new file mode 100644 index 000000000..7300e63f0 --- /dev/null +++ b/CI/AKS/docker/Dockerfile.mssql2014 @@ -0,0 +1,45 @@ +# Adapted from: https://github.com/Microsoft/mssql-docker/blob/master/windows/mssql-server-windows-developer/dockerfile +# +# +FROM mcr.microsoft.com/windows/servercore:ltsc2019 +LABEL maintainer "Liz B & Sebastian M" + +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Continue'; $ProgressPreference = 'SilentlyContinue';"] + +WORKDIR / + +# Install chocolatey via powershell +ENV chocolateyUseWindowsCompression false +RUN powershell -Command Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) +RUN choco feature disable --name showDownloadProgress + +# MSSQL 2014 Download links +ENV exe "http://download.microsoft.com/download/7/9/F/79F4584A-A957-436B-8534-3397F33790A6/SQLServer2014SP3-FullSlipstream-x64-ENU.exe" +ENV box "http://download.microsoft.com/download/7/9/F/79F4584A-A957-436B-8534-3397F33790A6/SQLServer2014SP3-FullSlipstream-x64-ENU.box" +ENV ACCEPT_EULA="Y" + +COPY /sxs /sources/sxs + +RUN Invoke-WebRequest -Uri $env:box -OutFile SQL.box ; \ + Invoke-WebRequest -Uri $env:exe -OutFile SQL.exe ; \ + Install-WindowsFeature -Name NET-Framework-Features -Source C:\sources\sxs -Verbose ; \ + Start-Process -Wait -FilePath .\SQL.exe -ArgumentList /qs, /x:setup ; \ + .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=0 /SQLSVCACCOUNT='NT AUTHORITY\System' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS ; \ + Remove-Item -Recurse -Force SQL.exe, SQL.box, setup, sources; \ + Get-Service MSSQLSERVER ; + +RUN stop-service MSSQLSERVER ; \ + set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql12.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpdynamicports -value '' ; \ + set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql12.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpport -value 1433 ; \ + set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql12.MSSQLSERVER\mssqlserver\' -name LoginMode -value 2 ; +HEALTHCHECK CMD [ "sqlcmd", "-Q", "select 1" ] + +COPY start.ps1 / +RUN net user /add LOCAL_SQLSRVR +RUN powershell -Command Add-LocalGroupMember -Group "Administrators" -Member "LOCAL_SQLSRVR" +USER LOCAL_SQLSRVR +CMD .\start -ACCEPT_EULA $env:ACCEPT_EULA -sqlsrvrlogin "LOCAL_SQLSRVR" -Verbose *> start_script.log + +# Install-WindowsFeature NET-Framework-Core +# Get-WindowsFeature -Name "NET-*" +# CMD while ($true) { Write-Output Sleep; Start-Sleep -Seconds 2 } diff --git a/CI/AKS/docker/Dockerfile.mssql2016 b/CI/AKS/docker/Dockerfile.mssql2016 new file mode 100644 index 000000000..f5dd7b994 --- /dev/null +++ b/CI/AKS/docker/Dockerfile.mssql2016 @@ -0,0 +1,37 @@ +# Adapted from: https://github.com/Microsoft/mssql-docker/blob/master/windows/mssql-server-windows-developer/dockerfile +# +# +FROM mcr.microsoft.com/windows/servercore:ltsc2019 +LABEL maintainer "Liz B & Sebastian M" + +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Continue'; $ProgressPreference = 'SilentlyContinue'; "] + +WORKDIR / + +# MSSQL 2016 Download link +ENV exe "https://go.microsoft.com/fwlink/?LinkID=799011" +ENV ACCEPT_EULA="Y" + +# Install MSSQL 2016 +RUN mode CON: CP /status ; \ + mkdir c:\downloadFiles ; \ + Invoke-WebRequest -Uri $env:exe -OutFile c:\downloadFiles\SQL.exe ; \ + mkdir c:\setupFiles ; \ + Start-Process -Wait -FilePath "c:\downloadFiles\SQL.exe" -ArgumentList /QUIET, /ACTION=Download, /MEDIAPATH="c:\setupFiles"; \ + ($setupExeFullPath = (Get-ChildItem -Path "c:\setupFiles\*.exe").FullName); \ + Start-Process -Wait -FilePath "$setupExeFullPath" -ArgumentList /qs, /x:"c:\setup" ; \ + c:\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=0 /SQLSVCACCOUNT='NT AUTHORITY\System' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS ; \ + Remove-Item -Recurse -Force c:\downloadFiles, c:\setupFiles, c:\setup ; \ + Get-Service -Name MSSQLSERVER + +RUN stop-service MSSQLSERVER ; \ + set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql13.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpdynamicports -value '' ; \ + set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql13.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpport -value 1433 ; \ + set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql13.MSSQLSERVER\mssqlserver\' -name LoginMode -value 2 ; +HEALTHCHECK CMD [ "sqlcmd", "-Q", "select 1" ] + +COPY start.ps1 / +RUN net user /add LOCAL_SQLSRVR +RUN powershell -Command Add-LocalGroupMember -Group "Administrators" -Member "LOCAL_SQLSRVR" +USER LOCAL_SQLSRVR +CMD .\start -ACCEPT_EULA $env:ACCEPT_EULA -sqlsrvrlogin "LOCAL_SQLSRVR" -Verbose *> start_script.log diff --git a/CI/AKS/docker/Dockerfile.mssql2017 b/CI/AKS/docker/Dockerfile.mssql2017 new file mode 100644 index 000000000..ea6cd0231 --- /dev/null +++ b/CI/AKS/docker/Dockerfile.mssql2017 @@ -0,0 +1,39 @@ +# Adapted from: https://github.com/Microsoft/mssql-docker/blob/master/windows/mssql-server-windows-developer/dockerfile +# +# +FROM mcr.microsoft.com/windows/servercore:ltsc2019 +LABEL maintainer "Liz B & Sebastian M" + +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Continue'; $ProgressPreference = 'SilentlyContinue';"] + +WORKDIR / + +# MSSQL 2017 Download links +ENV exe "https://go.microsoft.com/fwlink/?linkid=840945" +ENV box "https://go.microsoft.com/fwlink/?linkid=840944" +ENV ACCEPT_EULA="Y" + +# Install MSSQL 2017 +RUN mode CON: CP /status ; \ + CHCP ; \ + CHCP 437 ; \ + Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage" | Select-Object OEMCP, ACP ; \ + Invoke-WebRequest -Uri $env:box -OutFile SQL.box ; \ + Invoke-WebRequest -Uri $env:exe -OutFile SQL.exe ; \ + Start-Process -Wait -FilePath .\SQL.exe -ArgumentList /qs, /x:setup ; \ + .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=0 /SQLSVCACCOUNT='NT AUTHORITY\System' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS ; \ + Remove-Item -Recurse -Force SQL.exe, SQL.box, setup ; \ + Get-Service MSSQLSERVER ; + +RUN Get-Service MSSQLSERVER ; \ + Stop-Service MSSQLSERVER ; \ + Set-ItemProperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql14.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpdynamicports -value '' ; \ + Set-ItemProperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql14.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpport -value 1433 ; \ + Set-ItemProperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql14.MSSQLSERVER\mssqlserver\' -name LoginMode -value 2 ; +HEALTHCHECK CMD [ "sqlcmd", "-Q", "select 1" ] + +COPY start.ps1 / +RUN net user /add LOCAL_SQLSRVR +RUN powershell -Command Add-LocalGroupMember -Group "Administrators" -Member "LOCAL_SQLSRVR" +USER LOCAL_SQLSRVR +CMD .\start -ACCEPT_EULA $env:ACCEPT_EULA -sqlsrvrlogin "LOCAL_SQLSRVR" -Verbose *> start_script.log diff --git a/CI/AKS/docker/Dockerfile.mssql2019 b/CI/AKS/docker/Dockerfile.mssql2019 new file mode 100644 index 000000000..eb73eac0f --- /dev/null +++ b/CI/AKS/docker/Dockerfile.mssql2019 @@ -0,0 +1,41 @@ +# Adapted from: https://github.com/Microsoft/mssql-docker/blob/master/windows/mssql-server-windows-developer/dockerfile +# +# +FROM mcr.microsoft.com/windows/servercore:ltsc2019 +LABEL maintainer "Liz B & Sebastian M" + +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Continue'; $ProgressPreference = 'SilentlyContinue';"] + +WORKDIR / + +# Install chocolatey via powershell +ENV chocolateyUseWindowsCompression false +RUN powershell -Command Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) +RUN choco feature disable --name showDownloadProgress + +# MSSQL 2019 Download link +ENV exe "https://go.microsoft.com/fwlink/?linkid=866662" +ENV ACCEPT_EULA="Y" + +# Install MSSQL 2019 +RUN mkdir c:\downloadFiles ; \ + Invoke-WebRequest -Uri $env:exe -OutFile c:\downloadFiles\SQL.exe ; \ + mkdir c:\setupFiles ; \ + Start-Process -Wait -FilePath "c:\downloadFiles\SQL.exe" -ArgumentList /QUIET, /ACTION=Download, /MEDIAPATH="c:\setupFiles"; \ + $setupExeFullPath = (Get-ChildItem -Path "c:\setupFiles\*.exe").FullName; \ + $setupExecFullPath; \ + Start-Process -Wait -FilePath "$setupExeFullPath" -ArgumentList /qs, /x:"c:\setup" ; \ + c:\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=0 /SQLSVCACCOUNT='NT AUTHORITY\System' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS ; \ + Remove-Item -Recurse -Force c:\downloadFiles, c:\setupFiles, c:\setup + +RUN stop-service MSSQLSERVER ; \ + set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql15.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpdynamicports -value '' ; \ + set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql15.MSSQLSERVER\mssqlserver\supersocketnetlib\tcp\ipall' -name tcpport -value 1433 ; \ + set-itemproperty -path 'HKLM:\software\microsoft\microsoft sql server\mssql15.MSSQLSERVER\mssqlserver\' -name LoginMode -value 2 ; +HEALTHCHECK CMD [ "sqlcmd", "-Q", "select 1" ] + +COPY start.ps1 / +RUN net user /add LOCAL_SQLSRVR +RUN powershell -Command Add-LocalGroupMember -Group "Administrators" -Member "LOCAL_SQLSRVR" +USER LOCAL_SQLSRVR +CMD .\start -ACCEPT_EULA $env:ACCEPT_EULA -sqlsrvrlogin "LOCAL_SQLSRVR" -Verbose *> start_script.log diff --git a/CI/AKS/docker/README.md b/CI/AKS/docker/README.md new file mode 100644 index 000000000..0ae62ca8e --- /dev/null +++ b/CI/AKS/docker/README.md @@ -0,0 +1,13 @@ +# containerbuild + +1. `docker build -f /Dockerfile .` + 1. You can also change directories and run `docker build -f Dockerfile ..` +1. `docker container create -p HOSTPORT:1433 IMAGENAME` +1. `docker container start CONTAINERNAME` +1. `docker container exec 93d676bf0088 sqlcmd -E -S ".,1433" -Q "SELECT @@VERSION"` +1. `sqlcmd -U sa -P "W3lc0mE002" -S ".,41433" -Q "SELECT @@VERSION"` + +Changing the sa password. +`$cmd = "Invoke-SqlCmd -Query `"`"ALTER LOGIN sa WITH PASSWORD='test123451'`"`""` +`docker container exec CONTAINERNAMEorID powershell $cmd` +`sqlcmd -Q "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -S ".,41417" -U "sa" -P "test123451"` \ No newline at end of file diff --git a/CI/AKS/docker/changeSqlLogin.ps1 b/CI/AKS/docker/changeSqlLogin.ps1 new file mode 100644 index 000000000..78bed408c --- /dev/null +++ b/CI/AKS/docker/changeSqlLogin.ps1 @@ -0,0 +1,12 @@ +param( +[Parameter(Mandatory=$True)][string] $sa_password +) + +if($null -ne $sa_password -And $sa_password -ne "") { + Write-Verbose "Changing SA login credentials" + $sqlcmdChangePassword = "ALTER LOGIN sa with password='"+$sa_password+"' ,CHECK_POLICY=OFF,CHECK_EXPIRATION=OFF" + echo "command: " $sqlcmdChangePassword + & sqlcmd -Q $sqlcmdChangePassword + $sqlcmdEnable = "ALTER LOGIN sa ENABLE" + & sqlcmd -Q $sqlcmdEnable +} \ No newline at end of file diff --git a/CI/AKS/docker/start.ps1 b/CI/AKS/docker/start.ps1 new file mode 100644 index 000000000..28478583c --- /dev/null +++ b/CI/AKS/docker/start.ps1 @@ -0,0 +1,46 @@ +# The script starts the SQL Server Service + +param( +[Parameter(Mandatory=$false)] +[string]$ACCEPT_EULA, + +[Parameter(Mandatory=$false)] +[string]$sqlsrvrlogin +) + +Write-Host "Beginning of start.ps1" + +if($ACCEPT_EULA -ne "Y" -And $ACCEPT_EULA -ne "y") +{ + Write-Verbose "ERROR: You must accept the End User License Agreement before this container can start." + Write-Verbose "Set the environment variable ACCEPT_EULA to 'Y' if you accept the agreement." + + exit 1 +} + +# start the service +Write-Verbose "Starting SQL Server" +start-service MSSQLSERVER +Write-Verbose "Started SQL Server." + +# create the sql server login +Write-Verbose "Create login for sqlsrvrlogin account." + +$createLogin="CREATE LOGIN [" + $env:USERDOMAIN + "\"+ $sqlsrvrlogin +"] FROM WINDOWS" +Write-Verbose "Create login command: $createLogin" +& sqlcmd -j -m-1 -Q $createLogin + +Write-Verbose "Providing sysadmin permissions to sqlsrvrlogin account." + +$enableSysAdminPermissions="sp_addsrvRolemember [" + $env:USERDOMAIN + "\"+ $sqlsrvrlogin +"], 'sysadmin'" +Write-Verbose "Enable sys admin permissions command: $enableSysAdminPermissions" +& sqlcmd -j -m-1 -Q $enableSysAdminPermissions + +$lastCheck = (Get-Date).AddSeconds(-2) +while ($true) +{ + Get-EventLog -LogName Application -Source "MSSQL*" -After $lastCheck | Select-Object TimeGenerated, EntryType, Message + $lastCheck = Get-Date + Start-Sleep -Seconds 2 +} +Write-Error "Should not get here, ever." \ No newline at end of file diff --git a/CI/AKS/docker/sxs/Microsoft-Windows-InternetExplorer-Optional-Package~31bf3856ad364e35~amd64~en-US~.cab b/CI/AKS/docker/sxs/Microsoft-Windows-InternetExplorer-Optional-Package~31bf3856ad364e35~amd64~en-US~.cab new file mode 100644 index 000000000..e2c943591 Binary files /dev/null and b/CI/AKS/docker/sxs/Microsoft-Windows-InternetExplorer-Optional-Package~31bf3856ad364e35~amd64~en-US~.cab differ diff --git a/CI/AKS/docker/sxs/Microsoft-Windows-NetFx3-OnDemand-Package~31bf3856ad364e35~amd64~en-US~.cab b/CI/AKS/docker/sxs/Microsoft-Windows-NetFx3-OnDemand-Package~31bf3856ad364e35~amd64~en-US~.cab new file mode 100644 index 000000000..a9818f821 Binary files /dev/null and b/CI/AKS/docker/sxs/Microsoft-Windows-NetFx3-OnDemand-Package~31bf3856ad364e35~amd64~en-US~.cab differ diff --git a/CI/AKS/docker/sxs/microsoft-windows-internetexplorer-optional-package~31bf3856ad364e35~amd64~~.cab b/CI/AKS/docker/sxs/microsoft-windows-internetexplorer-optional-package~31bf3856ad364e35~amd64~~.cab new file mode 100644 index 000000000..4dd9d77b6 Binary files /dev/null and b/CI/AKS/docker/sxs/microsoft-windows-internetexplorer-optional-package~31bf3856ad364e35~amd64~~.cab differ diff --git a/CI/AKS/docker/sxs/microsoft-windows-netfx3-ondemand-package~31bf3856ad364e35~amd64~~.cab b/CI/AKS/docker/sxs/microsoft-windows-netfx3-ondemand-package~31bf3856ad364e35~amd64~~.cab new file mode 100644 index 000000000..ef9d619fa Binary files /dev/null and b/CI/AKS/docker/sxs/microsoft-windows-netfx3-ondemand-package~31bf3856ad364e35~amd64~~.cab differ diff --git a/CI/AKS/envSetup/buildAndPushImage.ps1 b/CI/AKS/envSetup/buildAndPushImage.ps1 new file mode 100644 index 000000000..d39239f8e --- /dev/null +++ b/CI/AKS/envSetup/buildAndPushImage.ps1 @@ -0,0 +1,22 @@ +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$mssqlVersion, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$acrURL, # eg. crn1234567890.azurecr.io + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$azSpCrBase64, + $debugOnString="false" +); + +$debugOn = ($debugOnString -eq "true"); +if ($debugOn) { + $DebugPreference = "Continue"; +} + +$decodedCreds = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("$azSpCrBase64")); +$creds = (ConvertFrom-Json -InputObject $decodedCreds) ; + +Set-Location -Path ~\sourceRepo\docker; + +docker login $acrURL --username $creds.clientId --password $creds.clientSecret + +docker build . --file Dockerfile.$mssqlVersion --isolation=process -t $acrURL/mssql:$mssqlVersion + +docker push $acrURL/mssql:$mssqlVersion diff --git a/CI/AKS/envSetup/createContainerBuildMachine.ps1 b/CI/AKS/envSetup/createContainerBuildMachine.ps1 new file mode 100644 index 000000000..4059c77d2 --- /dev/null +++ b/CI/AKS/envSetup/createContainerBuildMachine.ps1 @@ -0,0 +1,102 @@ +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$projectName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$azSecretsManagerName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$azResourceGroupName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$machineRGName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$repoURL, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$commitId, + [ValidateNotNullOrEmpty()][ValidateNotNullOrEmpty()][string]$machineName, + [ValidateNotNullOrEmpty()][string]$azVMSize = "Standard_D2s_v3", + [switch]$debugOn=$false +); + +# https://activedirectoryfaq.com/2017/08/creating-individual-random-passwords/ +function Get-RandomCharacters($length, $characters) { + $random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length } + $private:ofs="" + return [String]$characters[$random] +} + +function Get-MD5HashOfString($string) { + $stringAsStream = [System.IO.MemoryStream]::new(); + $writer = [System.IO.StreamWriter]::new($stringAsStream); + $writer.write($string); + $writer.Flush(); + $stringAsStream.Position = 0; + $hashedString = (Get-FileHash -InputStream $stringAsStream).Hash; + return [String]$hashedString; +} + +if ($debugOn) { + $DebugPreference = "Continue"; +} + +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; +Write-Debug ("Project Name: {0}" -f "$projectName"); +Write-Debug ("Az Resource Group Name: {0}" -f "$azResourceGroupName"); +Write-Debug ("Machine Resource Group Name: {0}" -f "$machineRGName"); +Write-Debug ("Secrets Manager Name: {0}" -f "$azSecretsManagerName"); +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + +# Create Username and Password + +# ^(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%\^&\*\(\)])[a-zA-Z\d!@#$%\^&\*\(\)]***12,123***$ +$part1 = (Get-RandomCharacters -length 10 -characters 'abcdefghiklmnoprstuvwxyz'); +$part2 = (Get-RandomCharacters -length 10 -characters '1234567890'); +$part3 = (Get-RandomCharacters -length 10 -characters 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'); +$part4 = (Get-RandomCharacters -length 10 -characters '!#$%^&*'); +$allParts = -join ((-join ($part1,$part2,$part3,$part4)).ToCharArray() | Get-Random -Shuffle); +$buildMachinePassword = ConvertTo-SecureString -String "$allParts" -AsPlainText -Force; + +$part1 = (Get-RandomCharacters -length 10 -characters 'abcdefghiklmnoprstuvwxyz'); +$part2 = (Get-RandomCharacters -length 5 -characters '1234567890'); +$buildMachineUserName = "User_" + (-join ((-join ($part1,$part2)).ToCharArray() | Get-Random -Shuffle)); +$secretBuildMachineUserName = ConvertTo-SecureString -String "$buildMachineUserName" -AsPlainText -Force; + +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; +Write-Debug ("buildMachineUserName: {0}" -f "$buildMachineUserName"); +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + +$LocationName = (Get-AzResourceGroup -Name $azResourceGroupName).location; +$ComputerName = $machineName; + +$networkSecurityGroupName = "nsg_$machineName" ; +$NetworkName = "net_$machineName" ; +$NICName = "nic_$machineName"; +$SubnetName = "sub_$machineName"; +$SubnetAddressPrefix = "10.0.0.0/24"; +$VnetAddressPrefix = "10.0.0.0/16"; + +$machineRGCreationLog = (New-AzResourceGroup -Name $machineRGName -Location $LocationName -Tag @{Department="tSQLtCI"; Ephemeral="True"} -Force | Out-String); + +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; +Write-Debug ("machineRGCreationLog: {0}" -f "$machineRGCreationLog"); +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + +$networkSecurityGroup = New-AzNetworkSecurityGroup -Name $networkSecurityGroupName -ResourceGroupName $machineRGName -Location $LocationName +$SingleSubnet = New-AzVirtualNetworkSubnetConfig -Name $SubnetName -AddressPrefix $SubnetAddressPrefix -NetworkSecurityGroup $networkSecurityGroup; +$Vnet = New-AzVirtualNetwork -Name $NetworkName -ResourceGroupName $machineRGName -Location $LocationName -AddressPrefix $VnetAddressPrefix -Subnet $SingleSubnet; +$NIC = New-AzNetworkInterface -Name $NICName -ResourceGroupName $machineRGName -Location $LocationName -SubnetId $Vnet.Subnets[0].Id; + +$Credential = New-Object System.Management.Automation.PSCredential ($buildMachineUserName, $buildMachinePassword); + +$VirtualMachine = New-AzVMConfig -VMName $machineName -VMSize $azVMSize; +$VirtualMachine = Set-AzVMOperatingSystem -VM $VirtualMachine -Windows -ComputerName $ComputerName -Credential $Credential -ProvisionVMAgent -EnableAutoUpdate; +$VirtualMachine = Add-AzVMNetworkInterface -VM $VirtualMachine -Id $NIC.Id; +$VirtualMachine = Set-AzVMSourceImage -VM $VirtualMachine -PublisherName 'MicrosoftWindowsServer' -Offer 'WindowsServer' -Skus '2019-Datacenter-Core-with-Containers' -Version latest; + +$azVM = New-AzVM -ResourceGroupName $machineRGName -Location $LocationName -VM $VirtualMachine -Verbose ; + +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; +Write-Debug ("azVM"); +Write-Debug ($azVM | Format-Table | Out-String); +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; +Write-Debug ("Get-ChildItem"); +Write-Debug (Get-ChildItem -Recurse -Path . | Format-Table | Out-String); +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + +$setupBuildMachinePath = (Split-Path $MyInvocation.MyCommand.Path -Parent) + '\setupBuildMachine.ps1'; +Invoke-AzVMRunCommand -ResourceGroupName $machineRGName -VMName $machineName -CommandId 'RunPowerShellScript' -ScriptPath $setupBuildMachinePath -Parameter @{repoURL = "$repoURL"; commitId = "$commitId"; debugOnString = "$debugOn"} + +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'buildMachineUser' -SecretValue $secretBuildMachineUserName; +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'buildMachinePassword' -SecretValue $buildMachinePassword; diff --git a/CI/AKS/envSetup/manualPrep.ps1 b/CI/AKS/envSetup/manualPrep.ps1 new file mode 100644 index 000000000..8da21c923 --- /dev/null +++ b/CI/AKS/envSetup/manualPrep.ps1 @@ -0,0 +1,94 @@ +Param( + [Parameter(Mandatory=$true,HelpMessage="projectName must be at least of length 3 no more than 50 and can contain only alphanumeric characters ([a-z], [A-Z], [0-9]) or underscores (_). Spaces are not allowed.")] + [ValidatePattern("^[0-9a-zA-Z_]{3,50}$")] + [string] $projectName = "db_actions", + [string] $subscriptionId = "default" +); + +# Make sure that the projectName parameter does not include characters not allowed in the GitHub Secrets names +# As of 2021-01-24, "Secret names can only contain alphanumeric characters ([a-z], [A-Z], [0-9]) or underscores (_). Spaces are not allowed." + +if($subscriptionId -ne "default") { + az account set $subscriptionId; +} + +$azSubscriptionId = az account show --query id -o tsv; + +Write-Output "πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– BASE64 SNIPPET πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’–"; +Write-Output "πŸ’–"; +Write-Output ("Deploying to Subscription Id: {0}" -f $azSubscriptionId); +Write-Output "πŸ’–"; +Write-Output "πŸ’–"; +Write-Output "Continuing in..."; +for ($i = 5; $i -gt 0; $i--){ + Write-Output "✨ $i ✨"; + Start-Sleep -Seconds 1; +} +$region = "eastus2" +$azResourceGroupName = "rg_" + $projectName +$azServicePrincipalName = "sp_" + $projectName + +# Create the resource group +az group create -l $region -n $azResourceGroupName + +# Ensure the resource group Provisioning State is Suceeded. For example: +$sleepInterval = 10; +$waitTimeLimit = 0; +while ("Succeeded" -ne (az group list --query "[?name=='$azResourceGroupName'].{provisioningState: properties.provisioningState}" -o tsv)) { + Start-Sleep $sleepInterval; + $waitTimeLimit += $sleepInterval; + if($waitTimeLimit -ge 60){ + throw "Something catastrophic has happened! The expected provisioning state was not found after $waitTimeLimit seconds."; + } + +} + +# Create the service principal. The contributor role is insufficient for attaching a newly created ACR to an AKS cluster. +# We must check that the clientSecret does not contain single or double quotes. +# If it does, the either the json snippet returned will be invalid (in the case of the double quote) +# or it will break AKS later down the line (in the case of the single quote). +Do { + Write-Output "Generating Credentials"; + $spCredential = az ad sp create-for-rbac -n "$azServicePrincipalName" --sdk-auth --role contributor --scopes "/subscriptions/$azSubscriptionId"; +} While ( + ($spCredential.Split("`r`n").Split("`r").Split("`n") | Where-Object { $_ -match "^\s*`"clientSecret`"\s*:\s*`"[^`"]*[`"'][^`"]*`"\s*,?\s*$" }).count -gt 0 +); + +$spCredential; + +Write-Output "πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– BASE64 SNIPPET πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’–"; +Write-Output "πŸ’–"; +[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($spCredential)); +Write-Output "πŸ’–"; +Write-Output "πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’–"; +Write-Output ""; +Write-Output ""; +Write-Output ""; +Write-Output "πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– INSTRUCTIONS πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’–"; +Write-Output "πŸ’–"; +Write-Output "πŸ’– Copy the base64 encoded snippet above and save it as the GitHub Secret `"AZ_SP_CRED_$projectName`"."; +Write-Output "πŸ’–"; +Write-Output "πŸ’– GitHub secrets can be set by going to Settings > Secrets > `"New repository secret`"."; +Write-Output "πŸ’–"; +Write-Output "πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’–"; + +$serviceProviders = 'Microsoft.KeyVault', 'Microsoft.Kubernetes', 'Microsoft.ContainerRegistry', 'Microsoft.ContainerService'; + +# Register required services +foreach ($item in $serviceProviders) { + az provider register --namespace $item; +} + +# Wait until all required services are registered. +Write-Output ""; +Write-Output "πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’–"; +Write-Output "πŸ’– Waiting for registration of service providers."; +Write-Output "πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’– πŸ’–"; +$waitTimeLimit = 0; +while ((az provider list --query ("[?contains('", ($serviceProviders -join '|'), "',namespace)].{registrationState: registrationState}" -join "") -o tsv) -join "" -ne "Registered" * $serviceProviders.count) { + Start-Sleep $sleepInterval; + $waitTimeLimit += $sleepInterval; + if($waitTimeLimit -ge 300){ + throw "Something catastrophic has happened! The expected registration states were not found after $waitTimeLimit seconds."; + } +} diff --git a/CI/AKS/envSetup/setup.ps1 b/CI/AKS/envSetup/setup.ps1 new file mode 100644 index 000000000..460e96318 --- /dev/null +++ b/CI/AKS/envSetup/setup.ps1 @@ -0,0 +1,122 @@ +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $projectName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $azServicePrincipalCredentials, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $sshPassphrase, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $linuxNodePoolDefaultVMSize, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $windowsNodePoolDefaultVMSize, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $kubernetesVersion, + [Parameter(Mandatory=$false)][Switch] $debugOn +); + +# https://activedirectoryfaq.com/2017/08/creating-individual-random-passwords/ +function Get-RandomCharacters($length, $characters) { + $random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length } + $private:ofs="" + return [String]$characters[$random] +} + +function Get-MD5HashOfString($string) { + $stringAsStream = [System.IO.MemoryStream]::new(); + $writer = [System.IO.StreamWriter]::new($stringAsStream); + $writer.write($string); + $writer.Flush(); + $stringAsStream.Position = 0; + $hashedString = (Get-FileHash -InputStream $stringAsStream).Hash; + return [String]$hashedString; +} + +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +if ($debugOn) { + $DebugPreference = "Continue" +} + +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; +Write-Debug ""; +Write-Debug ("ErrorActionPreference: {0}" -f "$ErrorActionPreference"); +Write-Debug ""; +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + +$azResourceGroupName = "rg_" + $projectName; +$region = (Get-AzResourceGroup -Name $azResourceGroupName).Location +$projectNameHash = (Get-MD5HashOfString($projectName)).Substring(0,10); +$azSecretsManagerName = "sm-" + $projectNameHash; +$aksClusterName = "aks-" + $projectNameHash; +$containerRegistryName = ("crn-" + $projectNameHash).Replace('-',''); +$aksWinUser = ("aksWinUser-" + $projectNameHash).Replace('-',''); +$aksWinNodePoolName = "akswin"; #What can I name my Windows node pools? You have to keep the name to a maximum of 6 (six) characters. This is a current limitation of AKS. (https://docs.microsoft.com/en-us/azure/aks/windows-faq) + +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; +Write-Debug ("Project Name: {0}" -f "$projectName"); +Write-Debug ("Region: {0}" -f "$region"); +Write-Debug ("Resource Group Name: {0}" -f "$azResourceGroupName"); +Write-Debug ("Secrets Manager Name: {0}" -f "$azSecretsManagerName"); +Write-Debug ("AKS Cluster Name: {0}" -f "$aksClusterName"); +Write-Debug ("Container Registry Name: {0}" -f "$containerRegistryName"); +Write-Debug ("AKS Win User Name: {0}" -f "$aksWinUser"); +Write-Debug ("AKS Win Node Pool Name: {0}" -f "$aksWinNodePoolName"); +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + +# Parse service principal credentials to extract the subscription id and client secret. +$convertedCredentials = (ConvertFrom-Json $azServicePrincipalCredentials) +$azServicePrincipalClientId = $convertedCredentials.clientId; +$azSubscriptionId = $convertedCredentials.subscriptionId; +$azServicePrincipalClientSecret = $convertedCredentials.clientSecret; + +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; +Write-Debug ("✨ azServicePrincipalClientId: {0}" -f "$azServicePrincipalClientId"); +Write-Debug "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + +$azServicePrincipalObjectId = (Get-AzADServicePrincipal -ApplicationId $azServicePrincipalClientId).Id + +# Set up Secrets Manager on Azure (AKV). If the AKV exists, throws a non-terminating error. +# This fails miserably if there exists a AKV soft-deleted in the same region with the same name. I don't see a way to turn off soft-delete. So if one exists, it requires manual intervention. Turns out that the minimim SoftDeleteRetentionInDays is the magic number 7. +New-AzKeyVault -VaultName "$azSecretsManagerName" -ResourceGroupName "$azResourceGroupName" -Location "$region" -SoftDeleteRetentionInDays 7 + +# TODO: + +# The Azure Key Vault RBAC is two separate levels, management and data. The Contributor role assigned above to the azure service principal as part of manualPrep.ps1 is for the management level. Additional permissions are required to manipulate the data level. (https://docs.microsoft.com/en-us/azure/key-vault/general/overview-security) +Set-AzKeyVaultAccessPolicy -VaultName "$azSecretsManagerName" -ResourceGroupName "$azResourceGroupName" -ObjectId $azServicePrincipalObjectId -PermissionsToSecrets Get,Set + +# ^(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%\^&\*\(\)])[a-zA-Z\d!@#$%\^&\*\(\)]***12,123***$ +$part1 = (Get-RandomCharacters -length 10 -characters 'abcdefghiklmnoprstuvwxyz'); +$part2 = (Get-RandomCharacters -length 10 -characters '1234567890'); +$part3 = (Get-RandomCharacters -length 10 -characters 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'); +$part4 = (Get-RandomCharacters -length 10 -characters '!#$%^&*'); +$allParts = -join ((-join ($part1,$part2,$part3,$part4)).ToCharArray() | Get-Random -Shuffle) +$aksPassword = ConvertTo-SecureString -String "$allParts" -AsPlainText -Force + +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'aksPassword' -SecretValue $aksPassword; +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'projectName' -SecretValue (ConvertTo-SecureString -String $projectName -AsPlainText -Force); +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'region' -SecretValue (ConvertTo-SecureString -String $region -AsPlainText -Force); +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'azResourceGroupName' -SecretValue (ConvertTo-SecureString -String $azResourceGroupName -AsPlainText -Force); +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'azSecretsManagerName' -SecretValue (ConvertTo-SecureString -String $azSecretsManagerName -AsPlainText -Force); +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'aksClusterName' -SecretValue (ConvertTo-SecureString -String $aksClusterName -AsPlainText -Force); +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'containerRegistryName' -SecretValue (ConvertTo-SecureString -String $containerRegistryName -AsPlainText -Force); +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'aksWinUser' -SecretValue (ConvertTo-SecureString -String $aksWinUser -AsPlainText -Force); +Set-AzKeyVaultSecret -VaultName "$azSecretsManagerName" -Name 'aksWinNodePoolName' -SecretValue (ConvertTo-SecureString -String $aksWinNodePoolName -AsPlainText -Force); + +# Create a Container Registry. If the ACR exists, throws a non-terminating error. +New-AzContainerRegistry -ResourceGroupName "$azResourceGroupName" -Name "$containerRegistryName" -Sku "Basic" + +# Suppress irritating warnings about breaking changes in New-AzAksCluster, "WARNING: Upcoming breaking changes in the cmdlet 'New-AzAksCluster' :The cmdlet 'New-AzAksCluster' is replacing this cmdlet. - The parameter : 'NodeVmSetType' is changing. - Change description : Default value will be changed from AvailabilitySet to VirtualMachineScaleSets. - The parameter : 'NetworkPlugin' is changing. - Change description : Default value will be changed from None to azure." +Set-Item Env:\SuppressAzurePowerShellBreakingChangeWarnings "true" + +$azServicePrincipalCreds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($azServicePrincipalClientId, (ConvertTo-SecureString -String $azServicePrincipalClientSecret -AsPlainText -Force)); + +# Set up ssh key pair (https://docs.microsoft.com/en-us/azure/virtual-machines/linux/mac-create-ssh-keys) +ssh-keygen -m PEM -t rsa -b 4096 -f ~/.ssh/id_rsa -N "$sshPassphrase" + +# Create "~/.azure/acsServicePrincipal.json" with the format {"$azSubscriptionId":{"service_principal":"$azServicePrincipalClientId","client_secret":"$azServicePrincipalClientSecret"}} +$fileContent = ('{"', $azSubscriptionId, '":{"service_principal":"', $azServicePrincipalClientId, '","client_secret":"', $azServicePrincipalClientSecret, '"}}' -join ""); +Set-Content -Path ~/.azure/acsServicePrincipal.json -Value $fileContent; + +# Create a new AKS Cluster with a single linux node +# TODO: Figure out if we can create a .json file for the service principal a la https://github.com/Azu re/azure-powershell/issues/13012 +New-AzAksCluster -ServicePrincipalIdAndSecret $azServicePrincipalCreds -ResourceGroupName "$azResourceGroupName" -Name "$aksClusterName" -NodeCount 1 -NetworkPlugin azure -NodeVmSetType VirtualMachineScaleSets -WindowsProfileAdminUserName "$aksWinUser" -WindowsProfileAdminUserPassword $aksPassword -KubernetesVersion "$kubernetesVersion" -NodeVmSize $linuxNodePoolDefaultVMSize; + +# Add a Windows Server node pool to our existing cluster +New-AzAksNodePool -ResourceGroupName "$azResourceGroupName" -ClusterName "$aksClusterName" -OsType Windows -Name "$aksWinNodePoolName" -VMSetType VirtualMachineScaleSets -Count 1 -KubernetesVersion "$kubernetesVersion" -VmSize $windowsNodePoolDefaultVMSize; + +$containerRegistryURL = "$containerRegistryName.azurecr.io"; +az aks get-credentials --resource-group "$azResourceGroupName" --name "$aksClusterName" +kubectl create secret docker-registry acr-secret --docker-server="$containerRegistryURL" --docker-username="$azServicePrincipalClientId" --docker-password="$azServicePrincipalClientSecret" diff --git a/CI/AKS/envSetup/setupBuildMachine.ps1 b/CI/AKS/envSetup/setupBuildMachine.ps1 new file mode 100644 index 000000000..b276a7a6e --- /dev/null +++ b/CI/AKS/envSetup/setupBuildMachine.ps1 @@ -0,0 +1,26 @@ +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$repoURL, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$commitId, + $debugOnString="false" +); + +$debugOn = ($debugOnString -eq "true"); +if ($debugOn) { + $DebugPreference = "Continue"; +} + +Set-ExecutionPolicy Bypass -Scope Process -Force; +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; +iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) ; +choco feature disable --name showDownloadProgress ; + +choco install git --force --force-dependencies -y; + +# Let's check stuff out in our home directory +Set-Location -Path ~ + +& 'C:\Program Files\Git\cmd\git.exe' clone $repoURL sourceRepo ; +Set-Location -Path sourceRepo ; +& 'C:\Program Files\Git\cmd\git.exe' checkout $commitId ; + +Get-ChildItem -Recurse -Path ./sourceRepo ; diff --git a/CI/AKS/envSetup/testBuildMachine.ps1 b/CI/AKS/envSetup/testBuildMachine.ps1 new file mode 100644 index 000000000..c35e33539 --- /dev/null +++ b/CI/AKS/envSetup/testBuildMachine.ps1 @@ -0,0 +1,11 @@ +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$repoURL, + $debugOnString="false" +); + +$debugOn = ($debugOnString -eq "true"); +if ($debugOn) { + $DebugPreference = "Continue"; +} + +Write-Output "Got here."; diff --git a/CI/AKS/mssql.deployment.windows-template.yml b/CI/AKS/mssql.deployment.windows-template.yml new file mode 100644 index 000000000..4a6263a75 --- /dev/null +++ b/CI/AKS/mssql.deployment.windows-template.yml @@ -0,0 +1,35 @@ +# Documentation +# - https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ +# - https://kubernetes.io/docs/tasks/inject-data-application/define-interdependent-environment-variables/ +# - +apiVersion: apps/v1 +kind: Deployment +metadata: + name: matrix.mssqlversion-deployment + labels: + app: matrix.mssqlversion-db +spec: + replicas: 1 + selector: + matchLabels: + app: matrix.mssqlversion-db + template: + metadata: + labels: + app: matrix.mssqlversion-db + spec: + nodeSelector: + agentpool: akswin + containers: + - name: matrix.mssqlversion-db + image: containerRegistryName/mssql:matrix.mssqlversion + ports: + - containerPort: 80 + name: other + protocol: TCP + - containerPort: 1433 + name: mssql + protocol: TCP + imagePullSecrets: + - name: acr-secret + diff --git a/CI/Azure-DevOps/.editorconfig b/CI/Azure-DevOps/.editorconfig new file mode 100644 index 000000000..bc0a98411 --- /dev/null +++ b/CI/Azure-DevOps/.editorconfig @@ -0,0 +1,5 @@ +[*] +indent_style = space +indent_size = 2 +insert_final_newline = true + diff --git a/CI/Azure-DevOps/AZ_MainPipeline.yml b/CI/Azure-DevOps/AZ_MainPipeline.yml new file mode 100644 index 000000000..9863f621b --- /dev/null +++ b/CI/Azure-DevOps/AZ_MainPipeline.yml @@ -0,0 +1,891 @@ +#tSQLt CI +name: tSQLt_CI_$(Date:yyyyMMdd)$(Rev:.r)($(Build.BuildId)) + +trigger: +- main +pr: none + +schedules: +- cron: 0 8 * * * + displayName: Daily Build of Main Branch + branches: + include: + - main + always: true + +pool: + vmImage: 'ubuntu-latest' + +parameters: # TODO, these don't work for scheduled pipelines, not even the defaults. Fix it. Hint: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/runtime-parameters?view=azure-devops&tabs=script#check-for-an-empty-parameter-object + - name: VMMatrix + type: object + default: + # - name: SQL2012 + # SQLVersionEdition: 2012Ent + # - name: SQL2014 + # SQLVersionEdition: 2014 + - name: SQL2016 + SQLVersionEdition: 2016 + # - name: SQL2017 + # SQLVersionEdition: 2017 + # - name: SQL2019 + # SQLVersionEdition: 2019 + # - name: SQL2022 + # SQLVersionEdition: 2022 + # - name: SQL2017Linux + # SQLVersionEdition: 2017L + # - name: SQL2019Linux + # SQLVersionEdition: 2019L + # - name: SQL2022Linux + # SQLVersionEdition: 2022L + - name: CreateEnvOnly + displayName: Create Environment Only + default: false + type: boolean + - name: VMPriority + displayName: VM Priority + type: string + default: Spot + values: + - Spot + - Regular + - Low + +variables: + - name: ArtifactBranchName + value: tSQLt-$(Build.SourceVersion)-$(Build.BuildId) + - name: VMSize + value: Standard_F4s_v2 #Standard_D2s_v3, Standard_B2s + - name: VMLocation + value: 'East US 2' + - name: NamePreFix + value: 'tSQLtCI_' + - name: buildDatabase + value: 'tSQLtCI' + - name: TSQLT_REPO_DIR + value: 'tSQLt' + - name: ARTIFACT_REPO_DIR + value: 'tSQLtArtifactRepo' + - name: CLR_ARTIFACT_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLtCLR + - name: TSQLTFILES_ARTIFACT_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLtBuild/Artifact + - name: DACPAC_ARTIFACT_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/DacpacBuild/Artifact + - name: TSQLT_PUBLIC_ARTIFACT_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLt/public + - name: TSQLT_VALIDATION_ARTIFACT_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLt/validation + - name: TSQLT_TEST_RESULTS_ARTIFACT_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLt/validation/TestResults + - name: TSQLT_BUILD_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLtBuild + - name: TSQLT_TESTS_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLtTests + - name: DACPAC_BUILD_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/DacpacBuild +# - name: SQLCMDPath +# # value: 'C:/Program Files/Microsoft SQL Server/110/Tools/Binn' for vmImage: 'vs2017-win2016' +# value: 'C:/Program Files/Microsoft SQL Server/Client SDK/ODBC/170/Tools/Binn' +# - name: SQLPackagePath +# # value: 'C:/Program Files/Microsoft SQL Server/150/DAC/bin' Updated to the new path on 2022-02-05 +# value: 'C:/Program Files/Microsoft SQL Server/160/DAC/bin' + +resources: + repositories: + - repository: tSQLtArtifacts + type: github + name: tSQLt/tmp6 + endpoint: GitHub-tSQLt-Robot + +stages: + +########################################################################################################## +## CREATE VMs / Containers ## +########################################################################################################## + + +- stage: Create_Environments + dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel + pool: + vmImage: 'windows-latest' + + jobs: + - job: Create + strategy: + matrix: + ${{ each version in parameters.VMMatrix }}: + ${{ format('{0}', version.name) }}: + SQLVersionEdition: ${{ version.SQLVersionEdition }} + SQLVersionName: ${{ version.name }} + + steps: + - checkout: self + clean: true + lfs: false + path: $(TSQLT_REPO_DIR) + + - task: AzureKeyVault@1 + inputs: + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' + KeyVaultName: 'tSQLtSigningKey' + SecretsFilter: '*' + RunAsPreJob: false + + - task: PowerShell@2 + name: CreateResourceGroupName + inputs: + workingDirectory: "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)" + targetType: 'inline' + script: | + $cfam = (Join-Path "./Build/" "CommonFunctionsAndMethods.psm1" | Resolve-Path) + + Write-Host "Attempting to load module from: $cfam" + Import-Module "$cfam" -Force + Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded + + $ResourceGroupName = ("$(NamePreFix)" + (Get-Date).tostring('yyyyMMdd') + "_" + "$(SQLVersionName)" + "_" + "$(Build.BuildId)"); + Log-Output "ResourceGroupName: $ResourceGroupName"; + Write-Host "##vso[task.setvariable variable=ResourceGroupName;isOutput=true]$ResourceGroupName"; + + - task: AzureCLI@2 + name: CreateSQLVMEnvironment + env: + SQL_USER_NAME: $(tSQLt-UserForCIEnvironment-UserName) + SQL_PASSWORD: $(tSQLt-UserForCIEnvironment-Password) + CONTACT_EMAIL: $(NotificationEmail) + VM_USER_NAME: $(tSQLt-AdminForCIVM-UserName) + VM_PASSWORD: $(tSQLt-AdminForCIVM-Password) + SQLPORTMINIMUM: $(SqlPortMinimum) + SQLPORTMAXIMUM: $(SqlPortMaximum) + inputs: + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' + azurePowerShellVersion: 'LatestVersion' + scriptType: ps + scriptLocation: inlineScript + inlineScript: | + Set-Location (Join-Path "$(Pipeline.Workspace)" "$(TSQLT_REPO_DIR)") + $cfam = (Join-Path "./Build/" "CommonFunctionsAndMethods.psm1" | Resolve-Path) + Write-Host "Attempting to load module from: $cfam" + Import-Module "$cfam" -Force + Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded + + $SqlPort = Get-Random -minimum $env:SQLPORTMINIMUM -maximum $env:SQLPORTMAXIMUM + $SQLUserName = "$env:SQL_USER_NAME"; + $SQLPwd = "$env:SQL_PASSWORD"; #TODO, randomize password, instead of taking it directly from the key vault + + if("$(SQLVersionEdition)" -like "[0-9][0-9][0-9][0-9]L"){ + $SqlPort = 1433; + $SQLUserName = 'SA' + + Log-Output '=========================================================='; + Log-Output 'Executing CreateSQLContainer.ps1'; + Log-Output '=========================================================='; + $Parameters = @{ + Location="$(VMLocation)"; + # Size="$(VMSize)" + ResourceGroupName="$(CreateResourceGroupName.ResourceGroupName)"; + BuildId="$(Build.BuildId)"; + SQLVersionEdition="$(SQLVersionEdition)"; + # SQLPort="1433"; + # SQLUserName="SA"; + SQLPwd="$SQLPwd"; + SQLCpu=3; + SQLMemory=8; + }; + $VMDetails = & 'CI/Azure-DevOps/CreateSQLContainer.ps1' @Parameters + + Log-Output '=========================================================='; + Log-Output 'Done: Executing CreateSQLContainer.ps1'; + Log-Output '=========================================================='; + }else{ + Log-Output '=========================================================='; + Log-Output 'Executing CreateSQLVM_azcli.ps1'; + Log-Output '=========================================================='; + $Parameters = @{ + Location="$(VMLocation)"; + Size="$(VMSize)" + ResourceGroupName="$(CreateResourceGroupName.ResourceGroupName)"; + BuildId="$(Build.BuildId)"; + VMAdminName="$env:VM_USER_NAME"; + VMAdminPwd="$env:VM_PASSWORD"; + SQLVersionEdition="$(SQLVersionEdition)"; + SQLPort="$SqlPort"; + SQLUserName="$SQLUserName"; + SQLPwd="$SQLPwd"; + VMPriority="${{ parameters.VMPriority }}"; + }; + $VMDetails = .'CI/Azure-DevOps/CreateSQLVM_azcli.ps1' @Parameters + + Log-Output '=========================================================='; + Log-Output 'Done: Executing CreateSQLVM_azcli.ps1'; + Log-Output '=========================================================='; + } + # $SerializedVMDetails=(ConvertTo-JSON -InputObject $VMDetails -Compress); + # $SerializedVMDetails; + + #-----------------------------------------------------------------------# + # The space below is required to make ANT work. (Not currently in use.) # + #---------------------------------------|-------------------------------# + $FQDNAndPort = $VMDetails.SQLVmFQDN + ", " + $VMDetails.SQLVmPort; + #---------------------------------------|-------------------------------# + #-----------------------------------------------------------------------# + + $ResourceGroupName = $VMDetails.ResourceGroupName; + Write-Host "##vso[task.setvariable variable=SQLUserName;isOutput=true]$SQLUserName" + Write-Host "##vso[task.setvariable variable=SQLPwd;isOutput=true]$SQLPwd" + Write-Host "##vso[task.setvariable variable=FQDNAndPort;isOutput=true]$FQDNAndPort" + Write-Host "##vso[task.setvariable variable=ResourceGroupName;isOutput=true]$ResourceGroupName"; + + # Write-Host "##vso[task.setvariable variable=SerializedVMDetails;isOutput=true]$SerializedVMDetails"; + + + - job: PrintSQLInfo + dependsOn: Create + strategy: + matrix: + ${{ each version in parameters.VMMatrix }}: + ${{ format('{0}', version.name) }}: + SQLVersionEdition: ${{ version.SQLVersionEdition }} + SQLVersionName: ${{ version.name }} + variables: + databaseAccessDetails: $[convertToJson(dependencies.Create.outputs)] + steps: + - checkout: none + - task: PowerShell@2 + name: PrintSQLInfo + inputs: + targetType: 'inline' + script: | + $inputObject = @' + $(databaseAccessDetails) + '@; + $myJsonObject = ConvertFrom-JSON -InputObject $inputObject; + $SQLUserNameKey = "$(System.JobName).CreateSQLVMEnvironment.SQLUserName"; + $SQLPwdKey = "$(System.JobName).CreateSQLVMEnvironment.SQLPwd"; + $FQDNAndPortKey = "$(System.JobName).CreateSQLVMEnvironment.FQDNAndPort"; + $SQLUserName = $myJsonObject.$SQLUserNameKey; + $SQLPwd = $myJsonObject.$SQLPwdKey; + $FQDNAndPort = $myJsonObject.$FQDNAndPortKey; + + $DS = Invoke-Sqlcmd -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "$FQDNAndPort" -Username "$SQLUserName" -Password "$SQLPwd" -As DataSet -TrustServerCertificate + $DS.Tables[0].Rows | %{ echo "{ $($_['U']), $($_['T']), $($_['V']) }" } + + if("${{ parameters.CreateEnvOnly }}" -ieq "true"){ + Write-Host '==========================================================' -ForegroundColor Yellow; + Write-Host "Name: $(SQLVersionName)"; + Write-Host "FQDN: $FQDNAndPort"; + Write-Host "User: $SQLUserName"; + Write-Host "Pass: $SQLPwd"; + Write-Host '==========================================================' -ForegroundColor Yellow; + + Write-Warning 'This information is now public! Run the following Powershell Statement to change the password immediately!'; + + $PwdChngCommand = "`$NewPwd = -join ((40..95+97..126) | Get-Random -Count 40 | % {[char]`$_}); Write-Output `"New Password: `$NewPwd`"; Invoke-Sqlcmd -Query `"ALTER LOGIN [$SQLUserName] WITH PASSWORD = '`$NewPwd';`" -ServerInstance `"$FQDNAndPort`" -Username `"$SQLUserName`" -Password `"$SQLPwd`" -TrustServerCertificate;"; + Write-Host '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - ' -ForegroundColor Yellow; + Write-Host $PwdChngCommand + Write-Host '==========================================================' -ForegroundColor Yellow; + + } + + +########################################################################################################## +## BUILD tSQLt - PART 1 ## +########################################################################################################## + + +- stage: Build_tSQLt_Part1 + dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel + condition: eq(${{ parameters.CreateEnvOnly }}, false) + + jobs: + + - job: CompileCLR + timeoutInMinutes: 10 + cancelTimeoutInMinutes: 2 + + # variables: + # CertificatePath: '$(Build.Repository.LocalPath)/tsqltclr/OfficialSigningKey/tSQLtOfficialSigningKey.pfx' + + steps: + + - checkout: self + clean: true + lfs: false + path: $(TSQLT_REPO_DIR) + + - task: AzureKeyVault@1 + inputs: + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' + KeyVaultName: 'tSQLtSigningKey' + + - task: PowerShell@2 + name: CompileCLR + inputs: + workingDirectory: $(Build.SourcesDirectory)/Build/ + targetType: 'inline' + script: | + + $pfxSecretBytes = [System.Convert]::FromBase64String('$(tSQLtOfficialSigningKey-Base64)') + $pfxFilePath = (Join-Path "$(Build.SourcesDirectory)" "/Build/tSQLtOfficialSigningKey.pfx") + [System.IO.File]::WriteAllBytes($pfxFilePath, $pfxSecretBytes) + $pfxPassword = (ConvertTo-SecureString -String '$(tSQLtSigningKeyPassword)' -Force -AsPlainText) + + & ./tSQLt_BuildCLR.ps1 -pfxFilePath $pfxFilePath -pfxPassword $pfxPassword + + - task: CopyFiles@2 + displayName: 'Copy all dll files to the ArtifactStagingDirectory' + inputs: + SourceFolder: ./Build/output/tSQLtCLR + Contents: 'tSQLtCLR.zip' + TargetFolder: '$(Build.ArtifactStagingDirectory)/tSQLtCLR.zip' + flattenFolders: true + + - task: PublishPipelineArtifact@1 + name: PublishCLRArtifact + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)/tSQLtCLR.zip' + artifact: 'tSQLtClrDlls' + publishLocation: 'pipeline' + + - job: BuildSQLScripts + timeoutInMinutes: 10 + cancelTimeoutInMinutes: 2 + dependsOn: CompileCLR + + steps: + + - checkout: self + clean: true + lfs: false + path: $(TSQLT_REPO_DIR) + + - task: PowerShell@2 + name: CreateArtifactDir + inputs: + workingDirectory: $(Build.SourcesDirectory)/Build/ + targetType: 'inline' + script: | + mkdir -p $(CLR_ARTIFACT_DIR) + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifactName: 'tSQLtClrDlls' + itemPattern: '*.zip' + targetPath: '$(CLR_ARTIFACT_DIR)' + + - task: PowerShell@2 + name: Build_tSQLt + inputs: + workingDirectory: $(Build.SourcesDirectory)/Build/ + targetType: 'inline' + script: | + & ./tSQLt_Build.ps1 + + - task: PowerShell@2 + name: Build_tSQLtTests + inputs: + workingDirectory: $(Build.SourcesDirectory)/Build/ + targetType: 'inline' + script: | + & ./tSQLt_BuildTests.ps1 + + - task: PowerShell@2 + name: CreateArtifact + inputs: + targetType: 'inline' + failOnStderr: true + script: | + $basePath = "$(Build.SourcesDirectory)/Build/output/"; + $artifactPath = '$(TSQLTFILES_ARTIFACT_DIR)'; + New-Item -Path $artifactPath -ItemType directory -Force + # $toBeCopied = @("Version.txt", "CommitId.txt", "tSQLt.Private_GetAssemblyKeyBytes.sql", "GetFriendlySQLServerVersion.sql", "CreateBuildLog.sql"); + $artifactFiles = @("Version.txt","CommitId.txt", "tSQLt.Private_GetAssemblyKeyBytes.sql", "GetFriendlySQLServerVersion.sql","CreateBuildLog.sql","tSQLtFiles.zip","tSQLtSnippets(SQLPrompt).zip"); + Get-ChildItem -Path ($basePath + "tSQLtBuild/*") -Include $artifactFiles | Copy-Item -Destination "$artifactPath"; + $artifactFiles = @("tSQLt.tests.zip"); + Get-ChildItem -Path ($basePath + "tSQLtTests/*") -Include $artifactFiles | Copy-Item -Destination "$artifactPath"; + + Set-Content -Path ($artifactPath+"CommitId.txt") -Value "$(Build.SourceVersion)" + + - task: PublishPipelineArtifact@1 + name: PublishtSQLtFilesArtifact + inputs: + targetPath: '$(TSQLTFILES_ARTIFACT_DIR)' + artifact: 'tSQLtFilesArtifact' + publishLocation: 'pipeline' + + +########################################################################################################## +## BUILD tSQLt - PART 2 ## +########################################################################################################## + + +- stage: Build_tSQLt_Part2 + dependsOn: + - Build_tSQLt_Part1 + - Create_Environments + + jobs: + - job: Build_Dacpac + + strategy: + matrix: + ${{ each version in parameters.VMMatrix }}: + ${{ format('{0}', version.name) }}: + SQLVersionEdition: ${{ version.SQLVersionEdition }} + + variables: + databaseAccessDetails: $[convertToJson(stageDependencies.Create_Environments.Create.outputs)] + + steps: + - checkout: self + clean: true + lfs: false + path: $(TSQLT_REPO_DIR) + + - task: DownloadPipelineArtifact@2 + name: DownloadtSQLtFilesArtifact + inputs: + buildType: 'current' + artifactName: 'tSQLtFilesArtifact' + targetPath: '$(TSQLTFILES_ARTIFACT_DIR)' + + - task: PowerShell@2 + displayName: 'Install SqlServer Module' + inputs: + pwsh: true + targetType: 'inline' + script: | + # Check if the SqlServer module is installed + $module = "SqlServer" + if (-not (Get-Module -ListAvailable -Name $module)) { + # Install the SqlServer module + Install-Module -Name $module -Scope CurrentUser -Force -AllowClobber + } + Import-Module $module + + + - task: PowerShell@2 + name: BuildDacpac + inputs: + pwsh: true + targetType: 'inline' + script: | + $BuildDir = (Join-Path "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)" "Build"|Resolve-Path); + Set-Location $BuildDir; + . (Join-Path $BuildDir 'SQLServerConnection.ps1'); + + $inputObject = @' + $(databaseAccessDetails) + '@; + + $myJsonObject = ConvertFrom-JSON -InputObject $inputObject; + + $SQLUserNameKey = "$(System.JobName).CreateSQLVMEnvironment.SQLUserName"; + $SQLPwdKey = "$(System.JobName).CreateSQLVMEnvironment.SQLPwd"; + $FQDNAndPortKey = "$(System.JobName).CreateSQLVMEnvironment.FQDNAndPort"; + + $SQLUserName = $myJsonObject.$SQLUserNameKey; + $SQLPwd = (ConvertTo-SecureString $myJsonObject.$SQLPwdKey -AsPlainText); + $FQDNAndPort = $myJsonObject.$FQDNAndPortKey; + $ApplicationName = "$(Build.DefinitionName)-$(Build.BuildId)-$(System.StageName)-$(System.JobId)" + $DatabaseName = "$(buildDatabase)_dacpac_src" + $SqlServerConnection = [SqlServerConnection]::new($FQDNAndPort,$SQLUserName,$SQLPwd,$ApplicationName); + + $__ = New-Item -ItemType "directory" -Path "./output/tSQLtTests/" + # $__ = New-Item -ItemType "directory" -Path "./output/tSQLtBuild/" + Move-Item (Join-Path '$(TSQLTFILES_ARTIFACT_DIR)' 'tSQLt.tests.zip') "./output/tSQLtTests/" + Move-Item (Join-Path '$(TSQLTFILES_ARTIFACT_DIR)' 'tSQLtFiles.zip') "./output/tSQLtBuild/" + & ./tSQLt_BuildDacpac.ps1 -SqlServerConnection $SqlServerConnection -DacPacDatabaseName $DatabaseName + + - task: PublishPipelineArtifact@1 + name: PublishtSQLtDacpacArtifact + inputs: + targetPath: '$(DACPAC_BUILD_DIR)' + artifact: 'tSQLtDacpacArtifact_$(System.JobName)' + publishLocation: 'pipeline' + + - job: Package_tSQLt_Artifact + dependsOn: Build_Dacpac + + steps: + + - checkout: self + clean: true + lfs: false + path: $(TSQLT_REPO_DIR) + + - task: DownloadPipelineArtifact@2 + name: DownloadtSQLtFilesArtifact + inputs: + buildType: 'current' + artifactName: 'tSQLtFilesArtifact' + targetPath: '$(Pipeline.Workspace)/tSQLtArtifactTmp' + + - task: DownloadPipelineArtifact@2 + name: DownloadtSQLtDacpacArtifact + inputs: + buildType: 'current' + patterns: '**/tSQLt*.*.dacpac' + targetPath: '$(Pipeline.Workspace)/dacpacArtifactTmp' + + - task: PowerShell@2 + name: CopyFilesToOutputDirs + inputs: + targetType: 'inline' + failOnStderr: true + script: | + $__ = New-Item -Path "$(TSQLT_BUILD_DIR)" -ItemType directory -Force; + $__ = New-Item -Path "$(TSQLT_TESTS_DIR)" -ItemType directory -Force; + $__ = New-Item -Path "$(DACPAC_BUILD_DIR)" -ItemType directory -Force; + + Get-ChildItem -Path "$(Pipeline.Workspace)/tSQLtArtifactTmp/" -Recurse; + + $files = @( + "$(Pipeline.Workspace)/tSQLtArtifactTmp/Version.txt", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/CommitId.txt", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/CreateBuildLog.sql", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/GetFriendlySQLServerVersion.sql", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/tSQLtSnippets(SQLPrompt).zip", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/tSQLtFiles.zip" + ); + $files|%{Move-Item $_ "$(TSQLT_BUILD_DIR)"} + Move-Item (Join-Path '$(Pipeline.Workspace)/tSQLtArtifactTmp' 'tSQLt.tests.zip') "$(TSQLT_TESTS_DIR)" + + Get-ChildItem -Path "$(Pipeline.Workspace)/dacpacArtifactTmp" -Filter *.dacpac -Recurse; + Get-ChildItem -Path "$(Pipeline.Workspace)/dacpacArtifactTmp" -Filter *.dacpac -Recurse | Copy-Item -Destination "$(DACPAC_BUILD_DIR)" + + Write-Host "CopyFilesToOutputDirs Results:" + Write-Host "----------------------------------------------------------------" + Get-ChildItem "$(TSQLT_BUILD_DIR)" -Recurse; + Write-Host "----------------------------------------------------------------" + Get-ChildItem "$(TSQLT_TESTS_DIR)" -Recurse; + Write-Host "----------------------------------------------------------------" + Get-ChildItem "$(DACPAC_BUILD_DIR)" -Recurse; + Write-Host "----------------------------------------------------------------" + + - task: PowerShell@2 + name: BuildtSQLtZip + inputs: + targetType: 'inline' + script: | + Set-Location "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)"; + ./Build/tSQLt_BuildPackage.ps1 + Get-ChildItem -Path "$(TSQLT_PUBLIC_ARTIFACT_DIR)" -Recurse; + Get-ChildItem -Path "$(TSQLT_VALIDATION_ARTIFACT_DIR)" -Recurse; + + - checkout: tSQLtArtifacts + clean: true + lfs: false + fetchDepth: 1 + persistCredentials: true + path: $(ARTIFACT_REPO_DIR) + + - task: AzureKeyVault@1 + inputs: + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' + KeyVaultName: 'tSQLtSigningKey' + + - task: PowerShell@2 + name: StoreInArtifactRepo + env: + GITHUB_USERNAME: $(GitHubUserName) + GITHUB_EMAIL: $(GitHubEmail) + inputs: + targetType: 'inline' + failOnStderr: false + ## git seems to print progress to stderror + script: | + $CheckIfGitOk = {param($isOk);if(-not $isOk){Write-Host "##vso[task.logissue type=error]GIT Failed!";throw "GIT Failed!"}}; + + Set-Location "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/"; + Write-Host ("ArtifactBranchName: {0}" -f "$(ArtifactBranchName)"); + try{ + git config --global user.email "$env:GITHUB_EMAIL" + git config --global user.name "$env:GITHUB_USERNAME" + git status + git remote show origin + + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + Write-Host ""; + git branch --remotes + Write-Host ""; + Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; + + git switch -c "$(ArtifactBranchName)" + $CheckIfGitOk.invoke($?); + + git rm -r *.* + $CheckIfGitOk.invoke($?); + New-Item -Path "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/public" -ItemType directory -Force; + New-Item -Path "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/validation" -ItemType directory -Force; + + Copy-Item -Path "$(TSQLT_PUBLIC_ARTIFACT_DIR)/*" -Destination "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/public" -Recurse -Verbose + Copy-Item -Path "$(TSQLT_VALIDATION_ARTIFACT_DIR)/*" -Destination "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/validation" -Recurse -Verbose + + Get-ChildItem -Path "./public" -Recurse; + Get-ChildItem -Path "./validation" -Recurse; + git status + + git add --all + git status + $CheckIfGitOk.invoke($?); + + git commit -m "$(ArtifactBranchName)" + $CheckIfGitOk.invoke($?); + + git push origin "$(ArtifactBranchName)" + $CheckIfGitOk.invoke($?); + }catch{ + throw "Git Failed. See prior errors."; + } + git status + + +########################################################################################################## +## VALIDATE ## +########################################################################################################## + + +- stage: Validate + displayName: ValidateAll + dependsOn: + - Build_tSQLt_Part2 + - Create_Environments + + jobs: + + - job: Validate + strategy: + matrix: + ${{ each version in parameters.VMMatrix }}: + ${{ format('{0}', version.name) }}: + SQLVersionEdition: ${{ version.SQLVersionEdition }} + + variables: + databaseAccessDetails: $[convertToJson(stageDependencies.Create_Environments.Create.outputs)] + + steps: + - checkout: self + clean: true + lfs: false + path: $(TSQLT_REPO_DIR) + + - checkout: tSQLtArtifacts + clean: true + lfs: false + fetchDepth: 1 + persistCredentials: true + path: $(ARTIFACT_REPO_DIR) + + - task: PowerShell@2 + name: GetArtifact + inputs: + targetType: 'inline' + script: | + $CheckIfGitOk = {param($isOk);if(-not $isOk){Write-Host "##vso[task.logissue type=error]GIT Failed!";throw "GIT Failed!"}}; + + Set-Location "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)"; + $ArtifactTargetPath= '$(TSQLT_PUBLIC_ARTIFACT_DIR)' + $TestsTargetPath= '$(TSQLT_VALIDATION_ARTIFACT_DIR)' + Write-Host ("ArtifactBranchName: {0}" -f "$(ArtifactBranchName)"); + + try{ + git fetch origin $(ArtifactBranchName):$(ArtifactBranchName) + $CheckIfGitOk.invoke($?); + git checkout $(ArtifactBranchName) + $CheckIfGitOk.invoke($?); + }catch{ + throw "git failed. See prior errors."; + } + + New-Item -Path $ArtifactTargetPath -ItemType directory -Force; + New-Item -Path $TestsTargetPath -ItemType directory -Force; + Copy-Item -Path "./public/*" -Verbose -Destination $ArtifactTargetPath; + Copy-Item -Path "./validation/*" -Verbose -Destination $TestsTargetPath; + Get-ChildItem -Path $ArtifactTargetPath -Recurse; + Get-ChildItem -Path $TestsTargetPath -Recurse; + + + - task: PowerShell@2 + displayName: 'Install SqlServer Module' + inputs: + pwsh: true + targetType: 'inline' + script: | + # Check if the SqlServer module is installed + $module = "SqlServer" + if (-not (Get-Module -ListAvailable -Name $module)) { + # Install the SqlServer module + Install-Module -Name $module -Scope CurrentUser -Force -AllowClobber + } + Import-Module $module + + + - task: PowerShell@2 + name: tSQLtValidateBuild + inputs: + pwsh: true + targetType: 'inline' + script: | + $BuildDir = (Join-Path "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)" "Build"|Resolve-Path); + Set-Location $BuildDir; + . (Join-Path $BuildDir 'SQLServerConnection.ps1'); + + $inputObject = @' + $(databaseAccessDetails) + '@; + + $myJsonObject = ConvertFrom-JSON -InputObject $inputObject; + + $SQLUserNameKey = "$(System.JobName).CreateSQLVMEnvironment.SQLUserName"; + $SQLPwdKey = "$(System.JobName).CreateSQLVMEnvironment.SQLPwd"; + $FQDNAndPortKey = "$(System.JobName).CreateSQLVMEnvironment.FQDNAndPort"; + + $SQLUserName = $myJsonObject.$SQLUserNameKey; + $SQLPwd = (ConvertTo-SecureString $myJsonObject.$SQLPwdKey -AsPlainText); + $FQDNAndPort = $myJsonObject.$FQDNAndPortKey; + $ApplicationName = "$(Build.DefinitionName)-$(Build.BuildId)-$(System.StageName)-$(System.JobId)" + $DatabaseName = "$(buildDatabase)_dacpac_src" + $SqlServerConnection = [SqlServerConnection]::new($FQDNAndPort,$SQLUserName,$SQLPwd,$ApplicationName); + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + MainTestDb = 'tSQLt.TmpBuild.ValidateBuild' + DacpacTestDb = 'tSQLt.TmpBuild.ValidateDacPac' + ExampleTestDb = 'tSQLt.TmpBuild.ValidateExample' + } + + & ./tSQLt_Validate.ps1 @parameters + + # - task: CmdLine@2 + # name: tSQLtValidateBuild + # inputs: + # script: | + # cd /d $(Pipeline.Workspace)/$(TSQLT_REPO_DIR) + # ECHO ON + # SET SQLInstanceName=$(SetupVariables.FQDNAndPort) + # SET DBName=$(buildDatabase) + # SET DBLogin=-U $(SetupVariables.SQLUserName) -P $(SetupVariables.SQLPwd) + # SET SQLCMDPath=$(SQLCMDPath) + # SET SQLPackagePath=$(SQLPackagePath) + # echo %SQLInstanceName% + # echo %DBName% + # echo %SQLCMDPath% + # echo %SQLPackagePath% + # type %0 + + # Build/LocalValidateBuild.bat "." "." "%SQLCMDPath%" "%SQLInstanceName%" tSQLt_Dev " %DBLogin%" "%SQLPackagePath%" -v || goto :error + # :error + + + - task: PublishTestResults@2 + condition: always() + inputs: + testResultsFormat: 'JUnit' # Options: JUnit, NUnit, VSTest, xUnit, cTest + testResultsFiles: '$(Pipeline.Workspace)/tSQLt/**/TestResults*.xml' + #searchFolder: '$(System.DefaultWorkingDirectory)' # Optional + #mergeTestResults: false # Optional + #failTaskOnFailedTests: false # Optional + testRunTitle: '$(SQLVersionEdition)' # Optional + buildPlatform: '$(SQLVersionEdition)' # Optional + #buildConfiguration: # Optional + publishRunAttachments: true # Optional + + - task: PublishPipelineArtifact@1 + name: PublishValidateOutputArtifact + inputs: + targetPath: '$(Pipeline.Workspace)/tSQLt/Build/output/Validate' + artifact: 'ValidateOutput_$(System.JobName)' + publishLocation: 'pipeline' + + + - task: PowerShell@2 + name: TagArtifact + inputs: + targetType: 'inline' + script: | + $CheckIfGitOk = {param($isOk);if(-not $isOk){Write-Host "##vso[task.logissue type=error]git failed!";throw "git failed!"}}; + + $TagName = "$(SQLVersionEdition)_$(Build.BuildId)" + + Set-Location "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/"; + + Write-Host ("ArtifactBranchName: {0}" -f "$(ArtifactBranchName)"); + try{ + git status + git remote show origin + + git switch "$(ArtifactBranchName)" + $CheckIfGitOk.invoke($?); + + git tag -f $TagName; + $CheckIfGitOk.invoke($?); + + git push --tags + $CheckIfGitOk.invoke($?); + + git status + }catch{ + throw "git failed. See prior errors."; + } + + +########################################################################################################## +## DELETE RESOURCES ## +########################################################################################################## + + +- stage: Delete_Resources + displayName: Delete Pipeline Resources + dependsOn: + - Create_Environments + - Validate + + condition: not(eq(${{ parameters.CreateEnvOnly }}, true)) + + pool: + vmImage: 'windows-latest' + + jobs: + - job: Delete_VM + + strategy: + matrix: + ${{ each version in parameters.VMMatrix }}: + ${{ format('{0}', version.name) }}: + SQLVersionEdition: ${{ version.SQLVersionEdition }} + + variables: + databaseAccessDetails: $[convertToJson(stageDependencies.Create_Environments.Create.outputs)] + + steps: + - checkout: self + clean: true + lfs: false + path: $(TSQLT_REPO_DIR) + + - task: AzureCLI@2 + name: DeleteAzureVM + inputs: + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' + azurePowerShellVersion: 'LatestVersion' + scriptType: ps + scriptLocation: inlineScript + inlineScript: | + Set-Location (Join-Path "$(Pipeline.Workspace)" "$(TSQLT_REPO_DIR)") + $cfam = (Join-Path "./Build/" "CommonFunctionsAndMethods.psm1" | Resolve-Path) + Write-Host "Attempting to load module from: $cfam" + Import-Module "$cfam" -Force + Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded + + $inputObject = @' + $(databaseAccessDetails) + '@; + $myJsonObject = ConvertFrom-JSON -InputObject $inputObject; + $ResourceGroupNameKey = "$(System.JobName).CreateResourceGroupName.ResourceGroupName"; + $ResourceGroupName = $myJsonObject.$ResourceGroupNameKey; + + $ResourceGroupName | Log-Output; + az group delete --name $ResourceGroupName --yes diff --git a/CI/Azure-DevOps/AZ_NightlyCleanup.yml b/CI/Azure-DevOps/AZ_NightlyCleanup.yml new file mode 100644 index 000000000..c06eb4e90 --- /dev/null +++ b/CI/Azure-DevOps/AZ_NightlyCleanup.yml @@ -0,0 +1,248 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +pool: + vmImage: 'windows-latest' #requires windows for azure authentication + +schedules: + - cron: 1 16 * * * + displayName: Scheduled NOON cleanup #Remove When Finished# + branches: + include: + - main + always: true + - cron: "0 4,5,6,7,8 * * *" + displayName: Nightly cleanup + branches: + include: + - main + always: true + +variables: + GetResourceGroupsStatement: 'Get-AzResourceGroup -Tag @{Department="tSQLtCI"; Ephemeral="True"}' + + +steps: + +- task: AzureCLI@2 + name: IdentifyDeletableRGs + condition: true + env: + SYSTEM_TEAMPROJECT: $(System.TeamProject) + SYSTEM_COLLECTIONURI: $(System.CollectionUri) + AZURE_DEVOPS_EXT_PAT: $(tSQLtCIAzureCLIPatToken) + ## black magic: this token ^^^^ is required to make "az pipelines runs show" work below + inputs: + azureSubscription: 'tSQLt CI - Nightly Cleanup - Service Connection' + scriptType: 'ps' + scriptLocation: 'inlineScript' + inlineScript: | + ##Functions + $GetAll_tSQLtCI_RGs = { + param(); + ((az group list)|Out-String|ConvertFrom-JSON|?{$_.Tags.Department -eq "tSQLtCI"}) + ## black magic, because "az group list" returns an array of strings that together make a json value -- go figure + } + + $DoesRGNotExist = { + param([object]$RGArray,[string]$name); + ($RGArray|Where-Object{$_.name -eq $name}).count -eq 0 + } + + $RGNs = @(); + $AddToRGNsAndListLocks_MethodWithSideEffect = { + param([string]$name); + $script:RGNs += ($name); + Write-Host ("{0} added to queue" -f $name); + ConvertFrom-JSON((az group lock list -g $name)|out-string)|FT; + } + + ##Script + Write-Host '-----------------------------' + Write-Host 'List RGs to be deleted' + Write-Host '-----------------------------' + $RGTBD = $GetAll_tSQLtCI_RGs.Invoke()|?{$_.Tags.Ephemeral -eq "True"}; + + $RGTBD | ForEach-Object { + $isDeletable = $TRUE; + $RBTag = $_.tags.RemovalBy; + + if(![string]::isnullorempty($RBTag)) + { + $isDeletable = $FALSE; + $stmt = ("az pipelines runs show --id $RBTag --query 'status' --organization='{0}' --project='{1}' --only-show-errors" -f $env:SYSTEM_COLLECTIONURI, $env:SYSTEM_TEAMPROJECT); + Write-Host ("Statement:{0}" -f "$stmt") + $Output = ((Invoke-Expression $stmt) 2>&1 6>&1); ##<<-- this will fail 5/19/2021 due to expired PAT token + ## more black magic, because "az pipelines" does not produce capturable errors + write-host ("-->{0}<--" -f $Output); + if($Output -match "The requested build $RBTag could not be found.") + { + $isDeletable = $TRUE; + write-host ("not found") + } + elseif($Output -match "^.?(cancelling|completed|notStarted|postponed).?$") + { + $isDeletable = $TRUE; + write-host ("not inProgress") + } + elseif($Output -match "^.?(inProgress).?$") + { + ##NoOp + write-host ("inProgress") + } + else + { + write-host ("Error encountered") + throw ("Error(s) encountered inquiring status for buid {0}{1}" -f $RBTag,$ConcatenatedErrorMessages) + } + + if($isDeletable) + { + write-host ("Removing RemovalBy Tag") + $MyRGN = $_.name; + az group update --name $MyRGN --set tags.RemovalBy; + } + } + + if($isDeletable) + { + $AddToRGNsAndListLocks_MethodWithSideEffect.Invoke($_.name); + } + else + { + Write-Host ("{0} skipped (RemovalBy {1})" -f $_.name,$RBTag); + } + } + + Write-Host '-----------------------------' + Write-Host 'Finding Orphan RGs' + Write-Host '-----------------------------' + + $AllRGs = $GetAll_tSQLtCI_RGs.Invoke(); + $ResourceGroupsWithParentRGNTag = $AllRGs|Where-Object{![string]::isnullorempty($_.tags.ParentRGN)} + $ResourceGroupsWithParentRGNTag|ForEach-Object{ + if($DoesRGNotExist.Invoke($AllRGs,$_.tags.ParentRGN)){ + $AddToRGNsAndListLocks_MethodWithSideEffect.Invoke($_.name); + } + } + + Write-Host '-----------------------------' + Write-Host 'Storing in Global Variable' + Write-Host '-----------------------------' + $SerializedListOfRGs=(ConvertTo-JSON -InputObject $RGNs -Compress) + $SerializedListOfRGs + Write-Host "##vso[task.setvariable variable=SerializedListOfRGs]$SerializedListOfRGs" + Write-Host 'Setting Variable Done' + + +- task: AzurePowerShell@4 + name: print_stuff + inputs: + azureSubscription: 'tSQLt CI - Nightly Cleanup - Service Connection' + azurePowerShellVersion: 'LatestVersion' + failOnStandardError: True + ScriptType: 'InlineScript' + Inline: | + Write-Host '----->' $env:SerializedListOfRGs '<-----' + $RGTBD = (ConvertFrom-JSON $env:SerializedListOfRGs) + $RGTBD | FT + $RGTBD.count; + Write-Host '-----> List of Running Cleanup Builds <-----' + #az pipelines runs list --org "https://sqlity.visualstudio.com/" --project "tSQLt CI" + + + +- task: AzurePowerShell@4 + condition: succeeded() + name: Delete_RGs + inputs: + azureSubscription: 'tSQLt CI - Nightly Cleanup - Service Connection' + azurePowerShellVersion: 'LatestVersion' + FailOnStandardError: true + ScriptType: 'InlineScript' + Inline: | + Set-Location $(Build.Repository.LocalPath) + .("Build/CommonFunctionsAndMethods.ps1") + + $ThreadHash = @{}; + $RGs = (ConvertFrom-JSON $env:SerializedListOfRGs); + Write-Host ("Deleting {0} ResourceGroups" -f $RGs.count) + $RunspacePool = [runspacefactory]::CreateRunspacePool(1,5); ##$RGs.count) + $RunspacePool.open() + $RGs | ForEach-Object { + $ResourceGroupName = $_; + Write-Host "==================================================================================================================="; + Write-Host ("Deleting ResourceGroup {0}" -f $ResourceGroupName) + if($ResourceGroupName -ne $null) + { + Write-Host ("BeginInvoke Remove-ResourceGroup -ResourceGroupName {0} -BuildId {1}" -f $ResourceGroupName, $(Build.BuildId)); + $scriptBlock = [scriptBlock]::Create("Remove-ResourceGroup -ResourceGroupName '$ResourceGroupName' -BuildId '$(Build.BuildId)'"); + $PSInstance = [powershell]::Create().AddScript({.("$(Build.Repository.LocalPath)\Build\CommonFunctionsAndMethods.ps1");}); + $PSInstance.AddScript($scriptBlock); + $PSinstance.RunspacePool = $RunspacePool; + $Handle = $PSInstance.BeginInvoke(); + $ThreadHash.Add($ResourceGroupName,@{PSInstance=$PSInstance;Handle=$Handle}) + }else{ + Write-Host "Skipped..."; + } + Write-Host ("Done BeginInvoke Remove-ResourceGroup -ResourceGroupName {0} -BuildId {1}" -f $ResourceGroupName, $(Build.BuildId)); + Write-Host "==================================================================================================================="; + } + $Errors = New-Object System.Collections.Generic.List[System.Object]; + $ThreadHash.GetEnumerator() | ForEach-Object{ + $Key = $_.Key; + Write-Host ":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"; + Write-Host ("START: EndInvoke Remove-ResourceGroup({0})" -f $Key); + $PsInstance = $_.Value.PsInstance; + $PSInstance.EndInvoke($_.Value.Handle); + $Streams = $PSInstance.Streams; + Write-Host "(Set [$VerbosePreference = "Continue"] and/or [$DebugPreference = "Continue"] to see the corresponding output below.)"; + $Streams.PSObject.Properties|ForEach-Object{ + Write-Host "⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩⇩"; + Write-Host $_.name; + Write-Host "β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’ β₯“β₯’"; + $_.value|ForEach-Object{Write-Host $_;}; + Write-Host "⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧⇧"; + } + if($Streams.Error.Count -ne 0) + { + $Errors.add($Key); + $Streams.Error|%{Write-Host "##vso[task.logissue type=error]$Key -> $_";} + } + Write-Host ("Done Remove-ResourceGroup({0})" -f $Key); + Write-Host ":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"; + } + if($Errors.count -ne 0) + { + $Errors|%{Write-Host ("##vso[task.logissue type=error]Error while removing ResourceGroup {0}" -f $_);} + throw("Errors encounterd while deleting resource group(s). See prior error messages."); + } + +- task: AzurePowerShell@4 + name: AssertDeleteSuccessful + condition: always() + inputs: + azureSubscription: 'tSQLt CI - Nightly Cleanup - Service Connection' + azurePowerShellVersion: 'LatestVersion' + failOnStandardError: True + ScriptType: 'InlineScript' + Inline: | + Write-Host '----->' $env:SerializedListOfRGs '<-----' + $RGTBD = (ConvertFrom-JSON $env:SerializedListOfRGs) + $NDC = 0; + $RGTBD | ForEach-Object{ + $RG = (Get-AzResourceGroup -name "$_" -ErrorVariable notPresent -ErrorAction SilentlyContinue); + if(-NOT ($notPresent)){ + $NDC++; + Write-Host ("##vso[task.logissue type=error]ResourceGroup {0} still exists!" -f $_); + }else{ + Write-Host ("ResourceGroup {0} deleted." -f $_); + } + }; + if($NDC -ne 0){ + Write-Host ("##vso[task.logissue type=error]{0} ResourceGroup(s) were not deleted!" -f $NDC); + throw("Not all resource groups were deleted sucessfully. See prior error messages."); + }; + diff --git a/CI/Azure-DevOps/AZ_TempTest.yml b/CI/Azure-DevOps/AZ_TempTest.yml new file mode 100644 index 000000000..30dab0304 --- /dev/null +++ b/CI/Azure-DevOps/AZ_TempTest.yml @@ -0,0 +1,56 @@ +name: $(Date:yyyyMMdd)$(Rev:.r)($(Build.BuildId)) + +trigger: none + +pr: none + +pool: + vmImage: 'windows-latest' + +parameters: +- name: SQLCMDPath + type: string + default: 'C:\Program Files\Microsoft SQL Server\110\Tools\Binn' +- name: RequestingBuildId + type: string + default: 'NULL' +- name: VMMatrix + type: object + default: [{"name":"S3",'SQLVersionEdition':'2016'},{"name":"S4",'SQLVersionEdition':'2017'},{"name":"S5",'SQLVersionEdition':'2019'}] + + + +variables: + - name: NamePreFix + value: 'tSQLtCI_DevTestLab_' + - name: ArtifactBranchName + value: tSQLt$(Build.SourceVersion) + +stages: +- stage: Find_SQLCMD + dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel + jobs: + - job: + steps: + + - task: PowerShell@2 + name: Find_SQLCMD + inputs: + targetType: 'inline' + script: | + $x = get-ChildItem -path C:\program* -include sqlcmd.exe -recurse -ErrorAction Ignore + $x|%{$_.FullName} + +- stage: Find_SqlPackage + dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel + jobs: + - job: + steps: + + - task: PowerShell@2 + name: Find_SqlPackage + inputs: + targetType: 'inline' + script: | + $x = get-ChildItem -path C:\program* -include sqlpackage.exe -recurse -ErrorAction Ignore + $x|%{$_.FullName} diff --git a/CI/Azure-DevOps/ContributorWithAuthorizationDelete.json b/CI/Azure-DevOps/ContributorWithAuthorizationDelete.json new file mode 100644 index 000000000..4d6cc21b2 --- /dev/null +++ b/CI/Azure-DevOps/ContributorWithAuthorizationDelete.json @@ -0,0 +1,20 @@ +{ + "Name": "ContributorWithAuthorizationDelete", + "Id": null, + "IsCustom": true, + "Description": "Lets you manage everything except access to resources.", + "Actions": [ + "*" + ], + "NotActions": [ + "Microsoft.Authorization/*/Write", + "Microsoft.Authorization/elevateAccess/Action", + "Microsoft.Blueprint/blueprintAssignments/write", + "Microsoft.Blueprint/blueprintAssignments/delete" + ], + "DataActions": [], + "NotDataActions": [], + "AssignableScopes": [ + "/subscriptions/58c04a99-5b92-410c-9e41-10262f68ca80" + ] +} \ No newline at end of file diff --git a/CI/Azure-DevOps/CreateSQLContainer.ps1 b/CI/Azure-DevOps/CreateSQLContainer.ps1 new file mode 100644 index 000000000..b6a95919b --- /dev/null +++ b/CI/Azure-DevOps/CreateSQLContainer.ps1 @@ -0,0 +1,98 @@ +<# USAGE: ./CreateSQLContainer.ps1 -Location "East US 2" -ResourceGroupName "myTestResourceGroup" -SQLVersionEdition "2022L" -SQLPwd "aoeihag;ladjfalkj46" -BuildId "001" #> +# using module "../../Build/CommonFunctionsAndMethods.psm1"; + +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Location, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ResourceGroupName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $BuildId, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLVersionEdition, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLPwd, + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][int] $SQLCpu = 4, + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][int] $SQLMemory = 8 +); +Write-Host "Starting execution of CreateSQLContainer.ps1" +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +$cfam = (Join-Path (Join-Path $invocationDir "../../Build/") "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Host "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force -Verbose +Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded + +$SQLPort = 1433; +$SQLUserName = 'SA'; + +$dir = $invocationDir; +$projectDir = Split-Path (Split-Path $dir); + +Log-Output "FileLocation: ", $dir; +Log-Output "Project Location: ", $projectDir; + + +Log-Output "*---------------*"; +Log-Output (az --version|Out-String); +Log-Output (bicep --version|Out-String); +Log-Output ($psversiontable|Out-String); +Log-Output "*---------------*"; + + +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "<-> <->"; +Log-Output "<-> START 1 <->"; +Log-Output "<-> <->"; +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "Parameters:"; +Log-Output "ResourceGroupName:", $ResourceGroupName; +Log-Output "Location:", $Location; +Log-Output "BuildId:", $BuildId; +Log-Output "SQLVersionEdition:", $SQLVersionEdition; +Log-Output "SQLPort:", $SQLPort; +Log-Output "<-> END 1 <-><-><-><-><-><-><-><-><-><-><-><-><->"; + +<# FYI Usage: $SQLVersionEditionHash.$SQLVersionEdition.offer = "SQL2016SP2-WS2016" #> +$SQLVersionEditionHash = @{ + "2017L"=@{"sqlversion"="2017";"image"="mcr.microsoft.com/mssql/server:2017-latest";}; + "2019L"=@{"sqlversion"="2019";"image"="mcr.microsoft.com/mssql/server:2019-latest";} + "2022L"=@{"sqlversion"="2022";"image"="mcr.microsoft.com/mssql/server:2022-latest";} +}; + +$SQLVersionEditionInfo = $SQLVersionEditionHash.$SQLVersionEdition; +Log-Output "SQLVersionEditionInfo: ", $SQLVersionEditionInfo; +$ContainerName = ("C{0}-{1}###############" -f $BuildId,$SQLVersionEdition).substring(0,15).replace('#','').ToLower() +$ContainerImage = $SQLVersionEditionInfo.image + +Log-Output 'Creating SQL Server Container resources' + +$templatePath = (Join-Path $invocationDir 'CreateSQLContainerTemplate.bicep' | Resolve-Path); +Log-Output "*---------------*"; +Log-Output $templatePath +Log-Output "*---------------*"; + +$deploymentResult = (az deployment sub create --name $ContainerName --location $Location --template-file $templatePath --parameters location=$Location containerName=$ContainerName sqlServerImage=$ContainerImage newResourceGroupName=$ResourceGroupName saPassword=$SQLPwd cpu=$SQLCpu memory=$SQLMemory); + +Log-Output 'Done: Creating SQL Server Container' +Log-Output 'Prep SQL Server for tSQLt Build' +# $deploymentResult +$outputs = ($deploymentResult|ConvertFrom-Json).properties.outputs +$ipAddress = $outputs.ipAddress.value +$Port = $outputs.Port.value +Log-Output "*---------------*"; +Log-Output "SQL Server address: $ipAddress,$Port" +Log-Output "*---------------*"; + +$GetSQLVersionPath = (Join-Path $invocationDir 'GetSQLServerVersion.sql' | Resolve-Path); +$DS = Invoke-Sqlcmd -InputFile $GetSQLVersionPath -ServerInstance "$ipAddress,$Port" -Username "$SQLUserName" -Password "$SQLPwd" -TrustServerCertificate -As DataSet +$DS.Tables[0].Rows | ForEach-Object{ Log-Output "{ $($_['LoginName']), $($_['TimeStamp']), $($_['VersionDetail']), $($_['ProductVersion']), $($_['ProductLevel']), $($_['SqlVersion']) }" } + +$ActualSQLVersion = $DS.Tables[0].Rows[0]['SqlVersion']; +Log-Output "Actual SQL Version:",$ActualSQLVersion; + +Log-Output 'Done: Prep SQL Server for tSQLt Build'; + +Return @{ + "VmName"="$ContainerName"; + "ResourceGroupName"="$ResourceGroupName"; + "SQLVmFQDN"="$ipAddress"; ##[vmname].[region].cloudapp.azure.com + "SQLVmPort"="$SQLPort"; ##1433 + "SQLVersionEdition"="$SQLVersionEdition"; ##2012Ent +}; diff --git a/CI/Azure-DevOps/CreateSQLContainerAndIpAddressModule.bicep b/CI/Azure-DevOps/CreateSQLContainerAndIpAddressModule.bicep new file mode 100644 index 000000000..5f952f748 --- /dev/null +++ b/CI/Azure-DevOps/CreateSQLContainerAndIpAddressModule.bicep @@ -0,0 +1,59 @@ +param location string = resourceGroup().location +param containerName string +param sqlServerImage string +param cpu int +param memory int +@secure() +param saPassword string + +var containerGroupName = '${containerName}-group' +var sqlPort = 1433 + +resource containerGroup 'Microsoft.ContainerInstance/containerGroups@2021-10-01' = { + name: containerGroupName + location: location + properties: { + containers: [ + { + name: containerName + properties: { + image: sqlServerImage + resources: { + requests: { + cpu: cpu + memoryInGB: memory + } + } + environmentVariables: [ + { + name: 'ACCEPT_EULA' + value: 'Y' + } + { + name: 'MSSQL_SA_PASSWORD' + secureValue: saPassword + } + ] + ports: [ + { + port: sqlPort + } + ] + } + } + ] + osType: 'Linux' + ipAddress: { + type: 'Public' + ports: [ + { + protocol: 'tcp' + port: sqlPort + } + ] + } + } +} + +output ipAddress string = containerGroup.properties.ipAddress.ip +output Port int = containerGroup.properties.ipAddress.ports[0].port diff --git a/CI/Azure-DevOps/CreateSQLContainerTemplate.bicep b/CI/Azure-DevOps/CreateSQLContainerTemplate.bicep new file mode 100644 index 000000000..dd0798986 --- /dev/null +++ b/CI/Azure-DevOps/CreateSQLContainerTemplate.bicep @@ -0,0 +1,37 @@ +targetScope='subscription' + +param location string = 'eastus2' +param newResourceGroupName string = 'sqlserver-container-test-rg' +param containerName string = 'sqlserver2022' +param sqlServerImage string = 'mcr.microsoft.com/mssql/server:2022-latest' +@secure() +param saPassword string +param cpu int = 4 +param memory int = 8 + + +resource newResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: newResourceGroupName + location: location + tags: { + Department: 'tSQLtCI' + Ephemeral: 'True' + } +} + + +module containers './CreateSQLContainerAndIpAddressModule.bicep' = { + name: 'deployContainers' + scope: newResourceGroup + params: { + location: location + containerName: containerName + sqlServerImage: sqlServerImage + cpu:cpu + memory:memory + saPassword: saPassword + } +} + +output ipAddress string = containers.outputs.ipAddress +output Port int = containers.outputs.Port diff --git a/CI/Azure-DevOps/CreateSQLVM.ps1 b/CI/Azure-DevOps/CreateSQLVM.ps1 new file mode 100644 index 000000000..e65cf4dc6 --- /dev/null +++ b/CI/Azure-DevOps/CreateSQLVM.ps1 @@ -0,0 +1,191 @@ +<# USAGE: ./CreateSQLVM.ps1 -Location "East US 2" -Size "Standard_D2as_v4" -ResourceGroupName "myTestResourceGroup" -VMAdminName "azureAdminName" -VMAdminPwd "aoeihag;ladjfalkj23" -SQLVersionEdition "2017" -SQLPort "41433" -SQLUserName "tSQLt_sa" -SQLPwd "aoeihag;ladjfalkj46" -BuildId "001" #> +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Location, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Size, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ResourceGroupName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $BuildId, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $VMAdminName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $VMAdminPwd, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLVersionEdition, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLPort, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLUserName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLPwd +); + +$scriptpath = $MyInvocation.MyCommand.Path; +$dir = Split-Path $scriptpath; +$projectDir = Split-Path (Split-Path $dir); + +.($projectDir+"\Build\CommonFunctionsAndMethods.ps1") +Log-Output "FileLocation: ", $dir; +Log-Output "Project Location: ", $projectDir; + + +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "<-> <->"; +Log-Output "<-> START 1 <->"; +Log-Output "<-> <->"; +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "Parameters:"; +Log-Output "ResourceGroupName:", $ResourceGroupName; +Log-Output "Location:", $Location; +Log-Output "Size:", $Size; +Log-Output "BuildId:", $BuildId; +Log-Output "SQLVersionEdition:", $SQLVersionEdition; +Log-Output "SQLPort:", $SQLPort; +Log-Output "<-> END 1 <-><-><-><-><-><-><-><-><-><-><-><-><->"; + +$VNetName = $ResourceGroupName+'_VNet'; +$SubnetName = $ResourceGroupName + '_Subnet' +$VMName = ("V{0}-{1}###############" -f $BuildId,$SQLVersionEdition).substring(0,15).replace('#','') +$PipName = $ResourceGroupName + '_' + $(Get-Random); +$NsgName = $ResourceGroupName + '_nsg'; +$InterfaceName = $ResourceGroupName + '_nic'; + + + +#[string] $ResourceGroupName, [string] $ResourceGroupName, [string] $VMName, [string] $VNetName, [string] $DTLVNetSubnetName +Log-Output "<-> START 2 <-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "Names:"; +Log-Output "ResourceGroupName: ", $ResourceGroupName; +Log-Output "VMName: ", $VMName; +Log-Output "VNetName: ", $VNetName; +Log-Output "SubnetName: ", $SubnetName; +Log-Output "PipName: ", $PipName; +Log-Output "NsgName: ", $NsgName; +Log-Output "InterfaceName: ", $InterfaceName; +Log-Output "<-> END 2 <-><-><-><-><-><-><-><-><-><-><-><-><->"; + +<# FYI Usage: $SQLVersionEditionHash.$SQLVersionEdition.offer = "SQL2016SP2-WS2016" #> +$SQLVersionEditionHash = @{ + "2008R2Ent"=@{"sqlversion"="2008R2";"offer"="SQL2008R2SP3-WS2008R2SP1";"publisher"="microsoftsqlserver";"sku"="Enterprise";"osType"="Windows";"version"="latest"}; + "2008R2Std"=@{"sqlversion"="2008R2";"offer"="SQL2008R2SP3-WS2008R2SP1";"publisher"="microsoftsqlserver";"sku"="Standard";"osType"="Windows";"version"="latest"}; + "2012Ent"=@{"sqlversion"="2012";"offer"="SQL2012SP4-WS2012R2";"publisher"="microsoftsqlserver";"sku"="Enterprise";"osType"="Windows";"version"="latest"}; + "2014"=@{"sqlversion"="2014";"offer"="sql2014sp3-ws2012r2";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest"}; + "2016"=@{"sqlversion"="2016";"offer"="SQL2016SP2-WS2016";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest"}; + "2017"=@{"sqlversion"="2017";"offer"="sql2017-ws2019";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest"}; + "2019"=@{"sqlversion"="2019";"offer"="sql2019-ws2019";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest"} +}; + +$SQLVersionEditionInfo = $SQLVersionEditionHash.$SQLVersionEdition; +Log-Output "SQLVersionEditionInfo: ", $SQLVersionEditionInfo; + +Log-Output "Creating Resource Group $ResourceGroupName"; +New-AzResourceGroup -Name "$ResourceGroupName" -Location "$Location" -Tag @{Department="tSQLtCI"; Ephemeral="True"} -Force|Out-String|Log-Output; +Log-Output "DONE: Creating Resource Group $ResourceGroupName"; + +Log-Output "Creating SubnetConfig"; +# Create a subnet configuration +$SubnetConfig = New-AzVirtualNetworkSubnetConfig -Name $SubnetName -AddressPrefix 192.168.1.0/24; +Log-Output "DONE: Creating SubnetConfig"; + +Log-Output "Creating VNet"; +# Create a virtual network +$Vnet = New-AzVirtualNetwork -ResourceGroupName $ResourceGroupName -Location $Location -Name $VNetName -AddressPrefix 192.168.0.0/16 -Subnet $SubnetConfig; +Log-Output "DONE: Creating VNet"; + +Log-Output "Creating PIP"; +# Create a public IP address and specify a DNS name +$Pip = New-AzPublicIpAddress -ResourceGroupName $ResourceGroupName -Location $Location -AllocationMethod Static -IdleTimeoutInMinutes 4 -Name $PipName; +$FQDN = $Pip.IpAddress; +Log-Output "FQDN: ", $FQDN; +Log-Output "DONE: Creating PIP"; + +Log-Output "Creating NSG"; +# Rule to allow remote desktop (RDP) +$NsgRuleRDP = New-AzNetworkSecurityRuleConfig -Name "RDPRule" -Protocol Tcp ` + -Direction Inbound -Priority 1000 -SourceAddressPrefix * -SourcePortRange * ` + -DestinationAddressPrefix * -DestinationPortRange 3389 -Access Allow; + +#Rule to allow SQL Server connections on port $SQLPort +$NsgRuleSQL = New-AzNetworkSecurityRuleConfig -Name "MSSQLRule" -Protocol Tcp ` + -Direction Inbound -Priority 1001 -SourceAddressPrefix * -SourcePortRange * ` + -DestinationAddressPrefix * -DestinationPortRange $SQLPort -Access Allow; + +# Create the network security group +$Nsg = New-AzNetworkSecurityGroup -ResourceGroupName $ResourceGroupName ` + -Location $Location -Name $NsgName ` + -SecurityRules $NsgRuleRDP,$NsgRuleSQL; +Log-Output "DONE: Creating NSG"; + +Log-Output "Creating NIC"; +# Create the Network Interface +$Interface = New-AzNetworkInterface -Name $InterfaceName ` + -ResourceGroupName $ResourceGroupName -Location $Location ` + -SubnetId $VNet.Subnets[0].Id -PublicIpAddressId $Pip.Id ` + -NetworkSecurityGroupId $Nsg.Id +Log-Output "DONE: Creating NIC"; + +$VNet|Out-String|Log-Output; + +Log-Output "Creating VM"; +# Define a credential object +$SecurePassword = ConvertTo-SecureString $VMAdminPwd -AsPlainText -Force; +$VMCredentials = New-Object System.Management.Automation.PSCredential ($VMAdminName, $securePassword); + +Log-Output "*-**-*"; + +# Create a virtual machine configuration +$VMConfig = New-AzVMConfig -VMName $VMName -VMSize $Size -Priority "Spot" | + Set-AzVMOperatingSystem -Windows -ComputerName $VMName -Credential $VMCredentials -ProvisionVMAgent -EnableAutoUpdate | + Set-AzVMSourceImage -PublisherName "MicrosoftSQLServer" -Offer $SQLVersionEditionInfo.offer -Skus $SQLVersionEditionInfo.sku -Version $SQLVersionEditionInfo.version | + Add-AzVMNetworkInterface -Id $Interface.Id + +Log-Output "*-**-*"; + +# Create the VM +$VMInfo = New-AzVM -ResourceGroupName $ResourceGroupName -Location $Location -VM $VMConfig; +$VM = (Get-AzResource -Name $VMName -ResourceType Microsoft.Compute/virtualMachines -ResourceGroupName $ResourceGroupName); +$VmResourceId = $VM.ResourceId; +Log-Output "VmResourceId: ", $VmResourceId; + +$PiPInfo = Get-AzPublicIpAddress -ResourceGroupName $ResourceGroupName; + +Log-Output "DONE: Creating VM"; +Log-Output 'Applying SqlVM Config' + +##Set-PSDebug -Trace 1; +$sqlVMParameters = @{ + "sqlPortNumber" = $SQLPort + "sqlAuthenticationLogin" = "$SQLUserName" + "sqlAuthenticationPassword" = "$SQLPwd" + "newVMName" = "$VMName" + "newVMRID" = "$VmResourceId" +}; + +Log-Output "*---------------*"; +Log-Output (Get-InstalledModule -Name Az|Out-String); +Log-Output (bicep --version|Out-String); +Log-Output (get-module -name az.resources -listavailable|Out-String); +Log-Output ($psversiontable|Out-String); +Log-Output ($env:PSModulePath -split ";"|Out-String); +Log-Output "*---------------*"; +Log-Output "$dir/CreateSQLVirtualMachineTemplate.bicep" +Log-Output (Get-Item -Path "$dir/CreateSQLVirtualMachineTemplate.bicep"|Out-String); +Log-Output "*---------------*"; + +#$SQLVM = New-AzResourceGroupDeployment -ResourceGroupName "$ResourceGroupName" -TemplateFile "$dir/CreateSQLVirtualMachineTemplate.json" -sqlPortNumber "$SQLPort" -sqlAuthenticationLogin "$SQLUserName" -sqlAuthenticationPassword "$SQLPwd" -newVMName "$VMName" -newVMRID "$VmResourceId" +#$SQLVM = New-AzResourceGroupDeployment -ResourceGroupName "$ResourceGroupName" -TemplateFile "$dir/CreateSQLVirtualMachineTemplate.bicep" -TemplateParameterObject $sqlVMParameters -Debug; +az deployment group create --resource-group $ResourceGroupName --template-file "$dir/CreateSQLVirtualMachineTemplate.bicep" --parameters sqlPortNumber=$SQLPort sqlAuthenticationLogin="$SQLUserName" sqlAuthenticationPassword="$SQLPwd" newVMName="$VMName" newVMRID="$VmResourceId" + +$SQLVM|Out-String|Log-Output; + +Log-Output 'Done: Applying SqlVM Config' +Log-Output 'Prep SQL Server for tSQLt Build' + + +$DS = Invoke-Sqlcmd -InputFile "$dir/GetSQLServerVersion.sql" -ServerInstance "$FQDN,$SQLPort" -Username "$SQLUserName" -Password "$SQLPwd" -As DataSet +$DS.Tables[0].Rows | %{ Log-Output "{ $($_['LoginName']), $($_['TimeStamp']), $($_['VersionDetail']), $($_['ProductVersion']), $($_['ProductLevel']), $($_['SqlVersion']) }" } + +$ActualSQLVersion = $DS.Tables[0].Rows[0]['SqlVersion']; +Log-Output "Actual SQL Version:",$ActualSQLVersion; + +Log-Output 'Done: Prep SQL Server for tSQLt Build'; + +Return @{ + "VmName"="$VmName"; + "ResourceGroupName"="$ResourceGroupName"; + "SQLVmFQDN"="$FQDN"; ##[vmname].[region].cloudapp.azure.com + "SQLVmPort"="$SQLPort"; ##1433 + "SQLVersionEdition"="$SQLVersionEdition"; ##2012Ent +}; \ No newline at end of file diff --git a/CI/Azure-DevOps/CreateSQLVM_azcli.ps1 b/CI/Azure-DevOps/CreateSQLVM_azcli.ps1 new file mode 100644 index 000000000..4376c65ff --- /dev/null +++ b/CI/Azure-DevOps/CreateSQLVM_azcli.ps1 @@ -0,0 +1,200 @@ +<# USAGE: + +az login +az account set --name "tSQLt CI Subscription" + +./CreateSQLVM_azcli.ps1 -Location "East US 2" -Size "Standard_D2as_v4" -ResourceGroupName "myTestResourceGroup" -VMAdminName "azureadminname" -VMAdminPwd "aoeihag;ladjfalkj23" -SQLVersionEdition "2017" -SQLPort "41433" -SQLUserName "tSQLt_sa" -SQLPwd "aoeihag;ladjfalkj46" -BuildId "001" -VmPriority "Spot" +#> +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Location, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Size, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ResourceGroupName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $BuildId, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $VMAdminName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $VMAdminPwd, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLVersionEdition, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLPort, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLUserName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLPwd, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $VMPriority +); + +Write-Host "Starting execution of CreateSQLVM_azcli.ps1" +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +$cfam = (Join-Path (Join-Path $invocationDir "../../Build/") "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Host "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force -Verbose +Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded + + +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "FileLocation: ", $invocationDir; +Log-Output "Parameters: ---------------------------"; +Log-Output "Location:", $Location; +Log-Output "Size:", $Size; +Log-Output "ResourceGroupName:", $ResourceGroupName; +Log-Output "BuildId:", $BuildId; +Log-Output "SQLVersionEdition:", $SQLVersionEdition; +Log-Output "SQLPort:", $SQLPort; +Log-Output "VMPriority:", $VMPriority; +Log-Output "Parameters: ---------------------------"; +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><->"; + +$VNetName = $ResourceGroupName+'_VNet'; +$SubnetName = $ResourceGroupName + '_Subnet' +$VMName = ("V{0}-{1}###############" -f $BuildId,$SQLVersionEdition).substring(0,15).replace('#','') +$PipName = $ResourceGroupName + '_' + $(Get-Random); +$NsgName = $ResourceGroupName + '_nsg'; +$InterfaceName = $ResourceGroupName + '_nic'; + +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "Names:"; +Log-Output "ResourceGroupName: ", $ResourceGroupName; +Log-Output "VMName: ", $VMName; +Log-Output "VNetName: ", $VNetName; +Log-Output "SubnetName: ", $SubnetName; +Log-Output "PipName: ", $PipName; +Log-Output "NsgName: ", $NsgName; +Log-Output "InterfaceName: ", $InterfaceName; +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><->"; + +<# FYI Usage: $SQLVersionEditionHash.$SQLVersionEdition.offer = "SQL2016SP2-WS2016" +URN for az cli --> $SQLVersionEditionInfo.publisher+":"+$SQLVersionEditionInfo.offer+":"+$SQLVersionEditionInfo.sku+":"+$SQLVersionEditionInfo.version +#> +$SQLVersionEditionHash = @{ + "2008R2Std"=@{"sqlversion"="2008R2";"offer"="SQL2008R2SP3-WS2008R2SP1";"publisher"="microsoftsqlserver";"sku"="Standard";"osType"="Windows";"version"="latest";"bicep"="CreateSqlVirtualMachineTemplate-2008R2.bicep"}; #MicrosoftSQLServer:SQL2008R2SP3-WS2008R2SP1:Standard:latest + "2012Ent"=@{"sqlversion"="2012";"offer"="SQL2012SP4-WS2012R2";"publisher"="microsoftsqlserver";"sku"="Enterprise";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"}; #MicrosoftSQLServer:SQL2012SP4-WS2012R2:Enterprise:latest + "2014"=@{"sqlversion"="2014";"offer"="sql2014sp3-ws2012r2";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"}; #MicrosoftSQLServer:sql2014sp3-ws2012r2:sqldev:latest + "2016"=@{"sqlversion"="2016";"offer"="sql2016sp3-ws2019";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"}; #MicrosoftSQLServer:sql2016sp2-ws2019:sqldev:latest + "2017"=@{"sqlversion"="2017";"offer"="sql2017-ws2019";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"}; #MicrosoftSQLServer:sql2017-ws2019:sqldev:latest + "2019"=@{"sqlversion"="2019";"offer"="sql2019-ws2022";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"} #MicrosoftSQLServer:sql2019-ws2019:sqldev:latest + "2022"=@{"sqlversion"="2022";"offer"="sql2022-ws2022";"publisher"="microsoftsqlserver";"sku"="sqldev-gen2";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"} #MicrosoftSQLServer:sql2022-ws2022:sqldev:latest +}; + +$SQLVersionEditionInfo = $SQLVersionEditionHash.$SQLVersionEdition; +$ImageUrn = $SQLVersionEditionInfo.publisher+":"+$SQLVersionEditionInfo.offer+":"+$SQLVersionEditionInfo.sku+":"+$SQLVersionEditionInfo.version; +$TemplateFile = (Join-Path $invocationDir $SQLVersionEditionInfo.bicep | Resolve-Path); +Log-Output "ImageUrn: ", $ImageUrn; +Log-Output "SQLVersionEditionInfo: ", $SQLVersionEditionInfo; +Log-Output "TemplateFile: ", $TemplateFile; + +Log-Output "START: Creating Resource Group $ResourceGroupName"; + +$output = az group create --location "$Location" --name "$ResourceGroupName" --tags Department="tSQLtCI" Ephemeral="True" | ConvertFrom-Json; +if (!$output) { + Write-Error "Error creating Resource Group"; + return +} +Log-Output "DONE: Creating Resource Group $ResourceGroupName"; + +Log-Output "START: Creating VNet $VNetName"; +$output = az network vnet create --name "$VNetName" --resource-group "$ResourceGroupName" --location $Location --address-prefixes 192.168.0.0/16 ` + --subnet-name "$SubnetName" --subnet-prefixes 192.168.1.0/24 | ConvertFrom-Json; +if (!$output) { + Write-Error "Error creating VNet $VNetName"; + return +} +Log-Output "DONE: Creating VNet $VNetName"; + +Log-Output "START: Creating PIP $PipName"; +$output = az network public-ip create --name $PipName --resource-group $ResourceGroupName --allocation-method Static --idle-timeout 4 ` + --location $Location | ConvertFrom-Json; +if (!$output) { + Write-Error "Error creating PIP"; + return +} +$FQDN = (az network public-ip show --resource-group $ResourceGroupName --name $PipName --query "ipAddress" --output tsv) +Log-Output "FQDN: ", $FQDN; +Log-Output "DONE: Creating PIP $PipName"; + +Log-Output "START: Creating NSG and Rules $NsgName --> nsg create"; +$output = az network nsg create --name $NsgName --resource-group $ResourceGroupName --location $Location | ConvertFrom-Json; +if (!$output) { + Write-Error "Error creating NIC"; + return +} +Log-Output "START: Creating NSG and Rules $NsgName --> nsg rule create --name `"RDPRule`""; +$DestPort = 3389; +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "ResourceGroupName: ", $ResourceGroupName; +Log-Output "NsgName: ", $NsgName; +Log-Output "DestPort: ", $DestPort; +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +$output = az network nsg rule create --name "RDPRule" --nsg-name $NsgName --priority 1000 --resource-group $ResourceGroupName --access Allow ` + --destination-address-prefixes '*' --destination-port-ranges $DestPort --direction Inbound --protocol Tcp --source-address-prefixes '*' ` + --source-port-ranges '*' | ConvertFrom-Json; +if (!$output) { + Write-Error "Error creating NIC RDPRule"; + return +} +Log-Output "START: Creating NSG and Rules $NsgName --> nsg rule create --name `"MSSQLRule`""; +$output = az network nsg rule create --name "MSSQLRule" --nsg-name $NsgName --priority 1001 --resource-group $ResourceGroupName --access Allow ` + --destination-address-prefixes '*' --destination-port-ranges $SQLPort --direction Inbound --protocol Tcp --source-address-prefixes '*' ` + --source-port-ranges '*' | ConvertFrom-Json; +if (!$output) { + Write-Error "Error creating NIC MSSQLRule"; + return +} +Log-Output "DONE: Creating NSG and Rules $NsgName"; + +Log-Output "START: Creating NIC $InterfaceName"; +$output = az network nic create --name $InterfaceName --resource-group $ResourceGroupName --subnet $SubnetName --vnet-name $VNetName ` + --location $Location --network-security-group $NsgName --public-ip-address $PipName | ConvertFrom-Json; +if (!$output) { + Write-Error "Error creating NIC"; + return +} +Log-Output "DONE: Creating NIC $InterfaceName"; + +Log-Output "Creating VM $VMName"; + +$output = az vm create --name "$VMName" --resource-group "$ResourceGroupName" --location "$Location" --admin-password "$VMAdminPwd" ` + --admin-username "$VMAdminName" --computer-name "$VMName" --image "$ImageUrn" --nics "$InterfaceName" --priority "$VMPriority" ` + --size $Size --data-disk-sizes-gb 8 | ConvertFrom-Json; +if (!$output) { + Log-Output "VMName: ", $VMName; + Log-Output "ResourceGroupName: ", $ResourceGroupName; + Log-Output "Location: ", $Location; + Log-Output "VMAdminPwd: ", $VMAdminPwd; #Starred out anyways + Log-Output "VMAdminName: ", $VMAdminName; + Log-Output "VMName: ", $VMName; + Log-Output "ImageUrn: ", $ImageUrn; + Log-Output "InterfaceName: ", $InterfaceName; + Log-Output "Size: ", $Size; + Write-Error "Error creating vm"; + return; +} + +$VMResourceId = (az vm show --resource-group $ResourceGroupName --name $VMName --query id --output tsv) +Log-Output "VmResourceId: ", $VmResourceId; +Log-Output "DONE: Creating VM $VMName"; + +Log-Output 'START: Applying SqlVM Config' +$output = az deployment group create --resource-group $ResourceGroupName --template-file "$TemplateFile" ` + --parameters sqlPortNumber=$SQLPort sqlAuthenticationLogin="$SQLUserName" sqlAuthenticationPassword="$SQLPwd" newVMName="$VMName" newVMRID="$VmResourceId" | ConvertFrom-Json; +if (!$output) { + Write-Error "Error creating SqlVM"; + return; +} +$SQLVM|Out-String|Log-Output; +Log-Output 'DONE: Applying SqlVM Config' + +Log-Output 'START: Prep SQL Server for tSQLt Build' +$GetSQLServerVersionPath = (Join-Path $invocationDir "GetSQLServerVersion.sql" | Resolve-Path) +$DS = Invoke-Sqlcmd -InputFile $GetSQLServerVersionPath -ServerInstance "$FQDN,$SQLPort" -Username "$SQLUserName" -Password "$SQLPwd" -As DataSet -TrustServerCertificate +$DS.Tables[0].Rows | %{ Log-Output "{ $($_['LoginName']), $($_['TimeStamp']), $($_['VersionDetail']), $($_['ProductVersion']), $($_['ProductLevel']), $($_['SqlVersion']), $($_['ServerCollation']) }" } + +$ActualSQLVersion = $DS.Tables[0].Rows[0]['SqlVersion']; +Log-Output "Actual SQL Version:",$ActualSQLVersion; + +Log-Output 'DONE: Prep SQL Server for tSQLt Build'; + +Return @{ + "VmName"="$VmName"; + "ResourceGroupName"="$ResourceGroupName"; + "SQLVmFQDN"="$FQDN"; ##[vmname].[region].cloudapp.azure.com + "SQLVmPort"="$SQLPort"; ##1433 + "SQLVersionEdition"="$SQLVersionEdition"; ##2012Ent +}; \ No newline at end of file diff --git a/CI/Azure-DevOps/CreateSqlVirtualMachineTemplate-2008R2.bicep b/CI/Azure-DevOps/CreateSqlVirtualMachineTemplate-2008R2.bicep new file mode 100644 index 000000000..3bcd13852 --- /dev/null +++ b/CI/Azure-DevOps/CreateSqlVirtualMachineTemplate-2008R2.bicep @@ -0,0 +1,59 @@ +param sqlConnectivityType string = 'Public' +param sqlPortNumber int = 41433 +param sqlStorageWorkloadType string = 'General' +param sqlStorageDisksConfigurationType string = 'NEW' +param sqlDataSettingsDefaultFilePath string = 'F:\\data' +param sqlDataSettingsLUNs array = [ + 0 +] +param sqlLogSettingsDefaultFilePath string = 'F:\\log' +param sqlLogSettingsLUNs array = [ + 0 +] +param sqlAutopatchingDayOfWeek string = 'Sunday' +param sqlAutopatchingStartHour int = 2 +param sqlAutopatchingWindowDuration int = 60 +param sqlAuthenticationLogin string = 'tSQLt' +param sqlAuthenticationPassword string = 'sdlfksdlkfjlsdkjf39939^' +param newVMName string = 'V1052sql2014sp3' +param newVMRID string = '/subscriptions/58c04a99-5b92-410c-9e41-10262f68ca80/resourceGroups/tSQLtCI_DevTestLab_20200318_1052-V1052sql2014sp3-155797/providers/Microsoft.Compute/virtualMachines/V1052sql2014sp3' + +resource newVMName_resource 'Microsoft.SqlVirtualMachine/SqlVirtualMachines@2017-03-01-preview' = { + name: newVMName + location: resourceGroup().location + properties: { + virtualMachineResourceId: newVMRID + sqlManagement: 'Full' + sqlServerLicenseType: 'PAYG' + autoPatchingSettings: { + enable: true + dayOfWeek: sqlAutopatchingDayOfWeek + maintenanceWindowStartingHour: sqlAutopatchingStartHour + maintenanceWindowDuration: sqlAutopatchingWindowDuration + } + keyVaultCredentialSettings: { + enable: false + credentialName: '' + } + storageConfigurationSettings: { + diskConfigurationType: sqlStorageDisksConfigurationType + sqlDataSettings : { + luns: sqlDataSettingsLUNs + defaultFilePath: sqlDataSettingsDefaultFilePath + } + sqlLogSettings: { + luns: sqlLogSettingsLUNs + defaultFilePath: sqlLogSettingsDefaultFilePath + } + storageWorkloadType: sqlStorageWorkloadType + } + serverConfigurationsManagementSettings: { + sqlConnectivityUpdateSettings: { + connectivityType: sqlConnectivityType + port: sqlPortNumber + sqlAuthUpdateUserName: sqlAuthenticationLogin + sqlAuthUpdatePassword: sqlAuthenticationPassword + } + } + } +} diff --git a/CI/Azure-DevOps/CreateSqlVirtualMachineTemplate.bicep b/CI/Azure-DevOps/CreateSqlVirtualMachineTemplate.bicep new file mode 100644 index 000000000..43d6cdfe7 --- /dev/null +++ b/CI/Azure-DevOps/CreateSqlVirtualMachineTemplate.bicep @@ -0,0 +1,66 @@ +param sqlConnectivityType string = 'Public' +param sqlPortNumber int = 41433 +param sqlStorageWorkloadType string = 'General' +param sqlStorageDisksConfigurationType string = 'NEW' +param sqlDataSettingsDefaultFilePath string = 'F:\\data' +param sqlDataSettingsLUNs array = [ + 0 +] +param sqlLogSettingsDefaultFilePath string = 'F:\\log' +param sqlLogSettingsLUNs array = [ + 0 +] +param sqlAutopatchingDayOfWeek string = 'Sunday' +param sqlAutopatchingStartHour int = 2 +param sqlAutopatchingWindowDuration int = 60 +param sqlAuthenticationLogin string = 'tSQLt' +param sqlAuthenticationPassword string = 'sdlfksdlkfjlsdkjf39939^' +param newVMName string = 'V1052sql2014sp3' +param newVMRID string = '/subscriptions/58c04a99-5b92-410c-9e41-10262f68ca80/resourceGroups/tSQLtCI_DevTestLab_20200318_1052-V1052sql2014sp3-155797/providers/Microsoft.Compute/virtualMachines/V1052sql2014sp3' + +resource newVMName_resource 'Microsoft.SqlVirtualMachine/SqlVirtualMachines@2017-03-01-preview' = { + name: newVMName + location: resourceGroup().location + properties: { + virtualMachineResourceId: newVMRID + sqlManagement: 'Full' + sqlServerLicenseType: 'PAYG' + autoPatchingSettings: { + enable: true + dayOfWeek: sqlAutopatchingDayOfWeek + maintenanceWindowStartingHour: sqlAutopatchingStartHour + maintenanceWindowDuration: sqlAutopatchingWindowDuration + } + keyVaultCredentialSettings: { + enable: false + credentialName: '' + } + storageConfigurationSettings: { + diskConfigurationType: sqlStorageDisksConfigurationType + sqlDataSettings : { + luns: sqlDataSettingsLUNs + defaultFilePath: sqlDataSettingsDefaultFilePath + } + sqlLogSettings: { + luns: sqlLogSettingsLUNs + defaultFilePath: sqlLogSettingsDefaultFilePath + } + storageWorkloadType: sqlStorageWorkloadType + } + serverConfigurationsManagementSettings: { + sqlConnectivityUpdateSettings: { + connectivityType: sqlConnectivityType + port: sqlPortNumber + sqlAuthUpdateUserName: sqlAuthenticationLogin + sqlAuthUpdatePassword: sqlAuthenticationPassword + } + sqlInstanceSettings: { + collation: 'SQL_Latin1_General_CP1_CS_AS' + maxDop: 0 + isOptimizeForAdHocWorkloadsEnabled: false + minServerMemoryMB: 0 + maxServerMemoryMB: 2147483647 + } + } + } +} diff --git a/CI/Azure-DevOps/GetSQLServerVersion.sql b/CI/Azure-DevOps/GetSQLServerVersion.sql new file mode 100644 index 000000000..242b2135c --- /dev/null +++ b/CI/Azure-DevOps/GetSQLServerVersion.sql @@ -0,0 +1,19 @@ + SELECT + SUSER_NAME() LoginName, + SYSDATETIME() [TimeStamp], + @@VERSION [VersionDetail], + SERVERPROPERTY('ProductVersion') AS ProductVersion, + SERVERPROPERTY('ProductLevel') AS ProductLevel, + CASE (SELECT PARSENAME(XX,4)+'.'+PARSENAME(XX,3) FROM (SELECT (CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(MAX)))XX)X) + WHEN '9.0' THEN '2005' + WHEN '10.0' THEN '2008' + WHEN '10.50' THEN '2008R2' + WHEN '11.0' THEN '2012' + WHEN '12.0' THEN '2014' + WHEN '13.0' THEN '2016' + WHEN '14.0' THEN '2017' + WHEN '15.0' THEN '2019' + WHEN '16.0' THEN '2022' + ELSE 'Unknown' + END SQLVersion, + SERVERPROPERTY('Collation') AS ServerCollation \ No newline at end of file diff --git a/CI/CI.ssmssqlproj b/CI/CI.ssmssqlproj new file mode 100644 index 000000000..3fc252749 --- /dev/null +++ b/CI/CI.ssmssqlproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Examples/BuildOrder.txt b/Examples/BuildOrder.txt index 33f4335ac..f25fb17df 100644 --- a/Examples/BuildOrder.txt +++ b/Examples/BuildOrder.txt @@ -1,4 +1,4 @@ CreateDatabase.sql -../Build/temp/tSQLt.class.sql +../Build/temp/tSQLtBuild/tSQLt.class.sql Source.sql Tests.sql diff --git a/Examples/CreateDatabase.sql b/Examples/CreateDatabase.sql index a7a52c621..7184639fd 100644 --- a/Examples/CreateDatabase.sql +++ b/Examples/CreateDatabase.sql @@ -24,7 +24,7 @@ USE tempdb; DROP DATABASE tSQLt_Example; '); -CREATE DATABASE tSQLt_Example WITH TRUSTWORTHY ON; +CREATE DATABASE tSQLt_Example WITH TRUSTWORTHY OFF; GO USE tSQLt_Example; GO diff --git a/Examples/Examples.sqlproj b/Examples/Examples.sqlproj new file mode 100644 index 000000000..5a624ab4e --- /dev/null +++ b/Examples/Examples.sqlproj @@ -0,0 +1,13 @@ + + + + + Examples + {D2F3BB37-831A-4D07-89CC-4B9E1C9E9C07} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + 1033, CI + + + + + \ No newline at end of file diff --git a/Examples/Examples.ssmssqlproj b/Examples/Examples.ssmssqlproj index 3e89e8054..547db2457 100644 --- a/Examples/Examples.ssmssqlproj +++ b/Examples/Examples.ssmssqlproj @@ -2,50 +2,37 @@ - - - 2014-11-24T18:12:22.0165888-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_Example - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - + - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + TestCaseTemplate.sql - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + TestThatExamplesAreDeployed.sql - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + Tests.sql - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + Source.sql - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + CreateDatabase.sql diff --git a/Experiments/AnnotationParser.sql b/Experiments/AnnotationParser.sql new file mode 100644 index 000000000..7e1e960e2 --- /dev/null +++ b/Experiments/AnnotationParser.sql @@ -0,0 +1,47 @@ +IF OBJECT_ID('dbo.rotiueortiuo') IS NOT NULL DROP PROCEDURE dbo.rotiueortiuo; +GO + +GO +/* create proc dbo.rotiueortiuo on 20200722 +/* Annotations/**/ +RETURN +--*//* +RETURN + --[@tSQLt:sdfsdfsd]() +RETURN +*/ +CREATE/*23423 +/*sdfkljh*//**/ +-sdfkljh*/PROCEDURE + +dbo.rotiueortiuo +AS +RETURN 1; +GO +DECLARE @D NVARCHAR(MAX) = (SELECT definition FROM sys.sql_modules AS SM WHERE SM.object_id = OBJECT_ID('dbo.rotiueortiuo')); + + +SELECT + * + FROM( + SELECT + LTRIM(RTRIM(CASE WHEN LEFT(Z.S,1) = NCHAR(10) THEN SUBSTRING(Z.S,2,LEN(Z.S)-1) ELSE Z.S END)) SS + FROM( + SELECT + SUBSTRING(Y.X,Y.no,LEAD(Y.no)OVER(ORDER BY Y.no)-Y.no-1) S + FROM( + SELECT + * + FROM (VALUES(LEFT(@D,CHARINDEX('CREATE',@D))))X(X) + CROSS APPLY tSQLt.F_Num(LEN(X.X)+2) AS FN WHERE SUBSTRING(NCHAR(13)+X.X+NCHAR(13),FN.no,1)=NCHAR(13) + )Y + )Z + )ZZ + WHERE ZZ.SS LIKE '%--[[]@tSQLt:%](%)%' +-- SELECT FN.no,CHAR(no),CAST(CHAR(no) AS BINARY(1)) FROM tSQLt.F_Num(255) AS FN + + SELECT + *, + SUBSTRING('/**/'+X.X+'/**/',FN.no,LEAD(FN.no)OVER(ORDER BY FN.no)-FN.no) + FROM (VALUES(@D))X(X) + CROSS APPLY tSQLt.F_Num(LEN(X.X)+2) AS FN WHERE SUBSTRING('/**/'+X.X+'/**/',FN.no,2)IN('/*','*/','--',CHAR(13)+CHAR(10)) diff --git a/Experiments/AsynchronousExecution.sql b/Experiments/AsynchronousExecution.sql new file mode 100644 index 000000000..e2a957398 --- /dev/null +++ b/Experiments/AsynchronousExecution.sql @@ -0,0 +1,131 @@ +USE master; +GO +--------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------- +ALTER DATABASE WaitForTester SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE; +GO +USE WaitForTester; +--------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------- +GO +IF(EXISTS(SELECT 1 FROM sys.services WHERE name = '//AsyncExec/AsyncExecSourceQueue/Service'))DROP SERVICE [//AsyncExec/AsyncExecSourceQueue/Service]; +IF(OBJECT_ID('AsyncExec.AsyncExecSourceQueue') IS NOT NULL)DROP QUEUE AsyncExec.AsyncExecSourceQueue; +IF(EXISTS(SELECT 1 FROM sys.service_contracts WHERE name = '//AsyncExec/ExecuteCommand/Contract'))DROP CONTRACT [//AsyncExec/ExecuteCommand/Contract]; +IF(EXISTS(SELECT 1 FROM sys.service_message_types WHERE name = '//AsyncExec/ExecuteCommand'))DROP MESSAGE TYPE [//AsyncExec/ExecuteCommand]; +DROP PROCEDURE IF EXISTS AsyncExec.AsyncExecTargetQueueProcessor; +DROP PROCEDURE IF EXISTS AsyncExec.QueueCommand; +DROP SCHEMA IF EXISTS AsyncExec; +GO +CREATE SCHEMA AsyncExec; +GO +CREATE MESSAGE TYPE [//AsyncExec/ExecuteCommand] VALIDATION = WELL_FORMED_XML; +GO +CREATE CONTRACT [//AsyncExec/ExecuteCommand/Contract]([//AsyncExec/ExecuteCommand] SENT BY INITIATOR); +GO +CREATE QUEUE AsyncExec.AsyncExecSourceQueue WITH STATUS=ON, RETENTION=OFF; +GO +CREATE SERVICE [//AsyncExec/AsyncExecSourceQueue/Service] ON QUEUE AsyncExec.AsyncExecSourceQueue([//AsyncExec/ExecuteCommand/Contract]); +GO +CREATE PROCEDURE AsyncExec.QueueCommand + @cmd NVARCHAR(MAX) +AS +BEGIN + DECLARE @DialogHandle UNIQUEIDENTIFIER; + DECLARE @Message XML; + BEGIN TRANSACTION; + BEGIN DIALOG @DialogHandle + FROM SERVICE [//AsyncExec/AsyncExecSourceQueue/Service] + TO SERVICE N'//AsyncExec/AsyncExecSourceQueue/Service' + ON CONTRACT [//AsyncExec/ExecuteCommand/Contract] + WITH ENCRYPTION = OFF; + SELECT @Message = (SELECT @cmd [cmd] FOR XML PATH(''),TYPE); + SEND ON CONVERSATION @DialogHandle + MESSAGE TYPE [//AsyncExec/ExecuteCommand] + (@Message); + SELECT @Message AS QueuedCommand; + COMMIT TRANSACTION; +END; +GO +CREATE PROCEDURE AsyncExec.AsyncExecTargetQueueProcessor +AS +BEGIN + DECLARE @DialogHandle UNIQUEIDENTIFIER; + DECLARE @MessageTypeName sysname; + DECLARE @Message XML; + DECLARE @ErrMessage VARCHAR(MAX) = NULL; + DECLARE @cmd NVARCHAR(MAX); + WHILE (1=1) + BEGIN + BEGIN TRY + BEGIN TRANSACTION; + WAITFOR + ( + RECEIVE TOP(1) + @DialogHandle = conversation_handle, + @MessageTypeName = message_type_name, + @Message = CONVERT(XML, message_body) + FROM AsyncExec.AsyncExecSourceQueue + ), TIMEOUT 100000; + + IF (@@ROWCOUNT = 0) + BEGIN + IF (@@TRANCOUNT > 0 ) ROLLBACK TRANSACTION; + BREAK; + END; + + SET @cmd = NULL; + IF @MessageTypeName = N'//AsyncExec/ExecuteCommand' + BEGIN + SELECT @cmd = @Message.value('/cmd[1]','NVARCHAR(MAX)'); + END CONVERSATION @DialogHandle; + END; + COMMIT TRANSACTION; + END TRY + BEGIN CATCH + IF (@@TRANCOUNT > 0 ) ROLLBACK TRANSACTION; + SET @ErrMessage = 'Error during reading of cmd. (ErrorNumber=' + CONVERT(VARCHAR(100), ERROR_NUMBER()) + ' : ErrorMessage=' + ERROR_MESSAGE()+')'; + BREAK; + END CATCH; + + IF(@cmd IS NOT NULL) + BEGIN + BEGIN TRY + EXEC(@cmd); + END TRY + BEGIN CATCH + SET @ErrMessage = 'Error during execution of cmd (ErrorNumber=' + CONVERT(VARCHAR(100), ERROR_NUMBER()) + ' : ErrorMessage=' + ERROR_MESSAGE()+'): '+@cmd; + BREAK; + END CATCH; + END; + END; + IF(@ErrMessage IS NOT NULL) + BEGIN + RAISERROR(@ErrMessage, 16, 1); + END; +END; +GO +ALTER QUEUE AsyncExec.AsyncExecSourceQueue WITH ACTIVATION(STATUS=ON,MAX_QUEUE_READERS=20,PROCEDURE_NAME=AsyncExec.AsyncExecTargetQueueProcessor,EXECUTE AS OWNER); +GO +--------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------- +GO +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 1000;'; +GO +SELECT * FROM AsyncExec.AsyncExecSourceQueue; +--SELECT * FROM sys.service_queues AS SQ +GO +--EXEC AsyncExec.AsyncExecTargetQueueProcessor; +SELECT X.[@b-@a],COUNT(1) cnt FROM(SELECT [@a],[@c],[@b],DATEDIFF(MILLISECOND,[@a],[@b])[@b-@a] FROM dbo.timelog AS T WITH(NOLOCK))X GROUP BY GROUPING SETS ((X.[@b-@a]),())ORDER BY X.[@b-@a]; +GO +SELECT * FROM sys.dm_exec_requests +SELECT * FROM sys.dm_exec_sessions AS DES \ No newline at end of file diff --git a/Experiments/CallParameters.sql b/Experiments/CallParameters.sql new file mode 100644 index 000000000..3724de09c --- /dev/null +++ b/Experiments/CallParameters.sql @@ -0,0 +1,58 @@ + +EXEC('PRINT ''$(parameter1)'';') + + + + +GO +CREATE PROCEDURE #somethingrather +AS +BEGIN + RETURN; +END; +GO +SELECT * FROM sys.procedures +SELECT * FROM tempdb.sys.procedures AS P +GO +IF OBJECT_ID('tempdb..#PrepareServer') IS NOT NULL DROP PROCEDURE tempdb..#PrepareServer; +EXEC(' +CREATE PROCEDURE tempdb..#PrepareServer +AS +BEGIN + SELECT DB_NAME(); +END; +'); +SELECT * FROM tempdb.sys.procedures AS P +EXEC tempdb..#PrepareServer; +GO +EXEC(' +CREATE TABLE #SuchATable(id INT) +'); +SELECT * FROM tempdb.sys.tables AS P +GO +CREATE PROCEDURE #ScopeTest +AS +BEGIN + EXEC('CREATE PROC #Inner1 AS BEGIN RETURN END;'); + EXEC #Inner1; +END; +GO +EXEC #ScopeTest + +GO +------------------------------------------------------------ +IF OBJECT_ID('') IS NOT NULL DROP PROCEDURE ; +IF OBJECT_ID('') IS NOT NULL DROP PROCEDURE ; +IF OBJECT_ID('') IS NOT NULL DROP PROCEDURE ; +IF OBJECT_ID('') IS NOT NULL DROP PROCEDURE ; +IF OBJECT_ID('') IS NOT NULL DROP PROCEDURE ; +IF OBJECT_ID('') IS NOT NULL DROP PROCEDURE ; +GO +CREATE PROCEDURE #... +GO +CREATE FUNCTION #xxx +RETURNS TABLE +AS +RETURN +SELECT 'abcd' xxx +GO diff --git a/Experiments/CertificateTest.sql b/Experiments/CertificateTest.sql new file mode 100644 index 000000000..0fc4a6b12 --- /dev/null +++ b/Experiments/CertificateTest.sql @@ -0,0 +1,9 @@ +IF(CERT_ID('tSQLtSigningKey') IS NOT NULL) DROP CERTIFICATE tSQLtSigningKey; +GO +CREATE CERTIFICATE tSQLtSigningKey +ENCRYPTION BY PASSWORD = 'password' +WITH SUBJECT = 'CN=tSQLt.org, title=tSQLt_OfficialSigningKey', + EXPIRY_DATE = '2020-12-01T00:00:00.000' +GO +BACKUP CERTIFICATE tSQLtSigningKey TO FILE ='C:\Data\git\tSQLt\tSQLt\tSQLtCLR\OfficialSigningKey\tSQLtSigningKey.cer' +WITH PRIVATE KEY(FILE='C:\Data\git\tSQLt\tSQLt\tSQLtCLR\OfficialSigningKey\tSQLtSigningKey.pkf', DECRYPTION BY PASSWORD = 'password', ENCRYPTION BY PASSWORD = 'password'); diff --git a/Experiments/CollationTests.sql b/Experiments/CollationTests.sql new file mode 100644 index 000000000..57f615de3 --- /dev/null +++ b/Experiments/CollationTests.sql @@ -0,0 +1,37 @@ +/*-- +RELEVANT LINKS +https://www.red-gate.com/simple-talk/databases/sql-server/t-sql-programming-sql-server/questions-sql-server-collations-shy-ask/ +https://docs.microsoft.com/en-us/sql/t-sql/statements/windows-collation-name-transact-sql?view=sql-server-ver15 +--*/ + +SELECT DATABASEPROPERTYEX('tempdb','Collation') [tempdb collation], + DATABASEPROPERTYEX(DB_NAME(),'Collation') [database collation] +GO + +DROP TABLE IF EXISTS dbo.atable; +DROP TABLE IF EXISTS tempdb..#Actual; +SELECT + 'Hello World!' COLLATE SQL_Polish_CP1250_CI_AS c1, + 'Hello World!' COLLATE SQL_Latin1_General_CP437_BIN c2, + 'Hello World!' COLLATE Albanian_BIN2 c3 + INTO dbo.atable +SELECT * INTO #Actual FROM dbo.atable + +SELECT 'dbo.atable' [table],* FROM sys.columns WHERE object_id = OBJECT_ID('dbo.atable') + +SELECT '#Actual',* FROM tempdb.sys.columns WHERE object_id = OBJECT_ID('tempdb..#Actual'); + +GO + +DROP TABLE IF EXISTS tempdb..#E; +CREATE TABLE #E( + C1 NVARCHAR(MAX) COLLATE DATABASE_DEFAULT, + C2 NVARCHAR(MAX) +); + +SELECT '#E',* FROM tempdb.sys.columns WHERE object_id = OBJECT_ID('tempdb..#E'); +GO +SELECT * +FROM #E E1 +JOIN #E E2 +ON E2.C2 = E1.C1 \ No newline at end of file diff --git a/Experiments/CreateSigningCertificate.ps1 b/Experiments/CreateSigningCertificate.ps1 new file mode 100644 index 000000000..470f04e21 --- /dev/null +++ b/Experiments/CreateSigningCertificate.ps1 @@ -0,0 +1,10 @@ +ο»Ώ# $cert = New-SelfSignedCertificate -Type Custom -KeySpec Signature ` +# -Subject "CN=P2SRootCert" -KeyExportPolicy Exportable ` +# -HashAlgorithm sha256 -KeyLength 2048 ` +# -CertStoreLocation "Cert:\CurrentUser\My" -KeyUsageProperty Sign -KeyUsage CertSign + +New-SelfSignedCertificate -Type Custom -DnsName P2SChildCert -KeySpec Signature ` +-Subject "CN=tSQLtOfficialSigningKey" -KeyExportPolicy Exportable ` +-HashAlgorithm sha256 -KeyLength 2048 ` +-CertStoreLocation "Cert:\CurrentUser\My" ` +-Signer $cert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2") \ No newline at end of file diff --git a/Experiments/CreateXEventSession_SSP&Batch.sql b/Experiments/CreateXEventSession_SSP&Batch.sql new file mode 100644 index 000000000..68b5145b8 --- /dev/null +++ b/Experiments/CreateXEventSession_SSP&Batch.sql @@ -0,0 +1,25 @@ +IF(EXISTS(SELECT * FROM sys.server_event_sessions WHERE name = 'MyXEventSession')) +BEGIN + DROP EVENT SESSION [MyXEventSession] ON SERVER; +END; +CREATE EVENT SESSION [MyXEventSession] ON SERVER +ADD EVENT sqlserver.rpc_completed ( + ACTION(sqlserver.sql_text, sqlserver.database_id, sqlserver.username) + WHERE (sqlserver.database_name=N'tSQLt_ValidateBuild') +), +ADD EVENT sqlserver.rpc_starting ( + ACTION(sqlserver.sql_text, sqlserver.database_id, sqlserver.username) + WHERE (sqlserver.database_name=N'tSQLt_ValidateBuild') +), +ADD EVENT sqlserver.sql_batch_completed ( + ACTION(sqlserver.sql_text, sqlserver.database_id, sqlserver.username) + WHERE (sqlserver.database_name=N'tSQLt_ValidateBuild') +), +ADD EVENT sqlserver.sql_batch_starting ( + ACTION(sqlserver.sql_text, sqlserver.database_id, sqlserver.username) + WHERE (sqlserver.database_name=N'tSQLt_ValidateBuild') +) +ADD TARGET package0.ring_buffer(SET max_memory=4096) -- Storing data in a ring buffer +WITH (MAX_MEMORY=4096 KB, EVENT_RETENTION_MODE=NO_EVENT_LOSS, MAX_DISPATCH_LATENCY=30 SECONDS, MAX_EVENT_SIZE=0 KB, MEMORY_PARTITION_MODE=NONE, TRACK_CAUSALITY=ON, STARTUP_STATE=OFF) +GO +ALTER EVENT SESSION [MyXEventSession] ON SERVER STATE = START; \ No newline at end of file diff --git a/Experiments/Debugging tSQLt.tdf b/Experiments/Debugging tSQLt.tdf new file mode 100644 index 000000000..86a69c6de Binary files /dev/null and b/Experiments/Debugging tSQLt.tdf differ diff --git a/Experiments/Experiments.sqlproj b/Experiments/Experiments.sqlproj new file mode 100644 index 000000000..0a5c37cb6 --- /dev/null +++ b/Experiments/Experiments.sqlproj @@ -0,0 +1,13 @@ + + + + + Experiments + {67F0113D-7E17-41F4-961D-FA1E88969F9C} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + 1033, CI + + + + + \ No newline at end of file diff --git a/Experiments/Experiments.ssmssqlproj b/Experiments/Experiments.ssmssqlproj index 0603c8934..7ea928e3d 100644 --- a/Experiments/Experiments.ssmssqlproj +++ b/Experiments/Experiments.ssmssqlproj @@ -2,81 +2,171 @@ - - - 2014-11-24T18:14:33.2545881-05:00 - SQL - Dev_tSQLt - - Windows Authentication - - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - + + + + + + AnnotationParser.sql + - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + AssertEqualsTable_PerformanceQueries.sql + + + + + AsynchronousExecution.sql + + + + + + CallParameters.sql + + + + + + CertificateTest.sql + + + + + + CollationTests.sql + - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + EXPERIMENTS.sql - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + FunctionBinding.sql - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + GenerateTestsForResultsetFilterDataTypes.sql - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + LIKE4000.sql + + + + + MSSQL Defect Try-Catch Invalidates Transaction.sql + - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + NameResolutionResearch.sql + + + + + ParsingDisaster.sql + + + + + + RunExternalAccessKeyExistsTests.sql + - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + SpProcPrototype.sql + + + + + SQLCmdTest.1.1.sql + + + + + + SQLCmdTest.1.2.sql + + + + + + SQLCmdTest.1.sql + + + + + + TestingTransactionNames.sql + + + + + + tSQLtListObjects.sql + + + + + + UnprepareServer.sql + + + + + + WAITFOR_DELAY_Tests.sql + + + + + + WaitForTester.sql + - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + XMLExperiment.sql - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt + + CaptureTestExecTimes.sql - + + + + + + RunTestsManually.bat + + \ No newline at end of file diff --git a/Experiments/GenerateLargeXMLResult.sql b/Experiments/GenerateLargeXMLResult.sql new file mode 100644 index 000000000..09848a8df --- /dev/null +++ b/Experiments/GenerateLargeXMLResult.sql @@ -0,0 +1,3 @@ + +SELECT (SELECT * FROM sys.all_objects CROSS JOIN (SELECT TOP(1) ROW_NUMBER()OVER(ORDER BY (SELECT 1)) n FROM sys.all_objects) n FOR XML PATH(''),TYPE,ROOT('GH')) + diff --git a/Experiments/GetDir.ps1 b/Experiments/GetDir.ps1 new file mode 100644 index 000000000..c5c4228f1 --- /dev/null +++ b/Experiments/GetDir.ps1 @@ -0,0 +1,4 @@ +ο»Ώ$scriptpath = $MyInvocation.MyCommand.Path +$dir = Split-Path $scriptpath +$scriptpath +$dir \ No newline at end of file diff --git a/Experiments/IncludeTest1.ps1 b/Experiments/IncludeTest1.ps1 new file mode 100644 index 000000000..2ef59a57f --- /dev/null +++ b/Experiments/IncludeTest1.ps1 @@ -0,0 +1 @@ +ο»Ώ$xxx=123 diff --git a/Experiments/IncludeTest2.ps1 b/Experiments/IncludeTest2.ps1 new file mode 100644 index 000000000..cc0bb2700 --- /dev/null +++ b/Experiments/IncludeTest2.ps1 @@ -0,0 +1,2 @@ +ο»Ώ.((Split-Path $MyInvocation.MyCommand.Path)+"/includeTest1.ps1") +$xxx diff --git a/Experiments/InvokeSqlcmdTest.ps1 b/Experiments/InvokeSqlcmdTest.ps1 new file mode 100644 index 000000000..c6cf423f2 --- /dev/null +++ b/Experiments/InvokeSqlcmdTest.ps1 @@ -0,0 +1,13 @@ + +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ServerName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Username, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $anotherString +); + +$SqlCmdVariables = "DatabaseName=master", "ColumnName=name"; +$Results = (Invoke-Sqlcmd -ServerInstance $ServerName -Username $Username -Password $anotherString -Query "SET STATISTICS XML ON; SELECT `$(ColumnName) FROM sys.databases WHERE [name] = '`$(DataBaseName)';" -Variable $SqlCmdVariables -MaxCharLength 10MB); +$Plan = [xml]$Results[$Results.GetUpperBound(0)].Item(0); +# $Plan.Save("C:\temp\test.sqlplan"); +$Plan; + diff --git a/Experiments/MSSQL Defect Try-Catch Invalidates Transaction.sql b/Experiments/MSSQL Defect Try-Catch Invalidates Transaction.sql new file mode 100644 index 000000000..6497e537a --- /dev/null +++ b/Experiments/MSSQL Defect Try-Catch Invalidates Transaction.sql @@ -0,0 +1,20 @@ +PRINT 'No TRY...CATCH:'; +EXEC ('BEGIN TRAN'); +SELECT XACT_STATE() AS [XACT_STATE()],@@TRANCOUNT AS [@@TRANCOUNT]; +GO +IF(XACT_STATE()<>0)ROLLBACK; +GO + + + +GO +PRINT 'In TRY...CATCH:'; +BEGIN TRY +EXEC ('BEGIN TRAN'); +END TRY +BEGIN CATCH +END CATCH; +SELECT XACT_STATE() AS [XACT_STATE()],@@TRANCOUNT AS [@@TRANCOUNT]; +GO +IF(XACT_STATE()<>0)ROLLBACK; +GO diff --git a/Experiments/NameResolutionResearch.sql b/Experiments/NameResolutionResearch.sql index 97f1b92ff..4cbf14805 100644 --- a/Experiments/NameResolutionResearch.sql +++ b/Experiments/NameResolutionResearch.sql @@ -180,7 +180,7 @@ tSQLt.FakeTable EXEC (@cmd); EXEC sys.sp_addextendedproperty - @name = N'tSQLt.FakeTable_OrgTableName', + @name = N'tSQLt.Private_TestDouble_OrgObjectName', @value = @newName, @level0type = N'SCHEMA', @level0name = @schemaName, -- @level1type = N'TABLE', @level1name = @tableName; -- @@ -210,7 +210,7 @@ tSQLt.ApplyConstraint WHERE class_desc = 'OBJECT_OR_COLUMN' AND major_id = OBJECT_ID(@schemaName + '.' + @tableName) -- AND minor_id = 0 - AND name = 'tSQLt.FakeTable_OrgTableName'; + AND name = 'tSQLt.Private_TestDouble_OrgObjectName'; SELECT @cmd = 'CONSTRAINT ' + name + ' CHECK' + definition FROM sys.check_constraints diff --git a/Experiments/ParsingDisaster.sql b/Experiments/ParsingDisaster.sql new file mode 100644 index 000000000..07e0b9447 --- /dev/null +++ b/Experiments/ParsingDisaster.sql @@ -0,0 +1,18 @@ +--execute, then comment out the line below and execute again +SELECT 1 AS [A +GO +SELECT 0 AS [X] + +--/* <-- execute, then delete '--' and execute again +/*Comment*/ +SELECT 2 +,2.2 +--*/SELECT 3 /* +--*/,3.2,' +SELECT 4 +,4.2 +--' /* +--*/SELECT 5 /* +--*/,5.2 /* +--*/ +SELECT 6 diff --git a/Experiments/ReadXEventSession.sql b/Experiments/ReadXEventSession.sql new file mode 100644 index 000000000..3ebf609f0 --- /dev/null +++ b/Experiments/ReadXEventSession.sql @@ -0,0 +1,18 @@ +DECLARE @xml_output xml; + +-- Get the XML data from the ring buffer +SELECT @xml_output = CAST(xet.target_data AS xml) +FROM sys.dm_xe_session_targets AS xet +JOIN sys.dm_xe_sessions AS xe +ON (xe.address = xet.event_session_address) +WHERE xe.name = N'MyXEventSession' -- Replace with your session name +AND xet.target_name = N'ring_buffer'; +-- SELECT @xml_output +-- Parse and display the relevant information +SELECT + event_data.value('(@timestamp)[1]', 'datetime2') AS EventTime, + event_data.value('(data[@name="duration"]/value)[1]', 'int') AS Duration, + event_data.value('(action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS SqlText, + event_data.query('.') xx +FROM @xml_output.nodes('RingBufferTarget/event') AS XEventData(event_data) +ORDER BY EventTime; diff --git a/Experiments/RunExternalAccessKeyExistsTests.sql b/Experiments/RunExternalAccessKeyExistsTests.sql new file mode 100644 index 000000000..8de0064cc --- /dev/null +++ b/Experiments/RunExternalAccessKeyExistsTests.sql @@ -0,0 +1,25 @@ +USE tSQLt_dev; +GO +DECLARE @cmd NVARCHAR(MAX); + +IF(CAST(SERVERPROPERTY('ProductMajorVersion') AS INT)<14) +BEGIN + PRINT '-------Dropping tSQLtAssemblyKey---------------'; + SET @cmd = N'IF SUSER_ID(''tSQLtAssemblyKey'') IS NOT NULL DROP LOGIN tSQLtAssemblyKey;'; + EXEC master.sys.sp_executesql @cmd; + SET @cmd = N'IF ASYMKEY_ID(''tSQLtAssemblyKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtAssemblyKey;'; + EXEC master.sys.sp_executesql @cmd; +END; +ELSE +BEGIN + PRINT '-------Dropping of tSQLtAssemblyKey skipped---------------'; +END; +GO +EXEC tSQLt.InstallAssemblyKey; +GO +EXECUTE AS LOGIN='tSQLt.Build.SA' +GO +EXEC tSQLt.Run 'EnableExternalAccessTests'; +GO +REVERT +GO diff --git a/Experiments/RunTestsManually.bat b/Experiments/RunTestsManually.bat new file mode 100644 index 000000000..08482c7b4 --- /dev/null +++ b/Experiments/RunTestsManually.bat @@ -0,0 +1,21 @@ +SQLCMD -S .\R2A -E -I -d tempdb -i ..\Build\temp\ResetValidationServer.sql +SQLCMD -S .\R2A -E -I -d tempdb -i ..\Build\temp\PrepareServer.sql +SQLCMD -S .\R2A -E -I -d tempdb -i ..\Build\temp\temp_prepare_server.sql -v NewDbName=tSQLt_dev +SQLCMD -S .\R2A -E -I -d tSQLt_dev -i ..\Build\temp\tSQLt.class.sql +REM SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\Drop(master.tSQLt_testutil).sql" +REM SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\Install(master.tSQLt_testutil).sql" +REM SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\Drop(tSQLtAssemblyKey)(Pre2017).sql" +REM SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\BootStrapTest.sql" +SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\TestUtil.sql" +SQLCMD -S .\R2A -E -I -d tSQLt_dev -Q "EXEC tSQLt_testutil.PrepMultiRunLogTable;EXEC tSQLt.SetSummaryError @SummaryError=0;" + +SQLCMD -S .\R2A -E -I -d tSQLt_dev -Q "EXEC tSQLt.Reset;" +SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\temp_executeas_sa.sql" ..\Build\temp\AllTests.SA.sql -v DbName="tSQLt_dev" ExecuteStatement="" +REM SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\temp_executeas_sa.sql" -v DbName="tSQLt_dev" ExecuteStatement="EXEC tSQLt.SetVerbose @Verbose = 1;EXEC tSQLt.RunNew;" +SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\temp_executeas_sa.sql" -v DbName="tSQLt_dev" ExecuteStatement="EXEC tSQLt.SetVerbose @Verbose = 1;EXEC tSQLt.Run '[RemoveAssemblyKeyTests].[test login with control server permission can execute procedure]';" + +SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\Install(tSQLtAssemblyKey).sql" +SQLCMD -S .\R2A -E -I -d tSQLt_dev -Q "EXEC tSQLt.Reset;" +SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\temp_executeas_sa.sql" ..\Build\temp\AllTests.EXTERNAL_ACCESS_KEY_EXISTS.sql -v DbName="tSQLt_dev" ExecuteStatement="" +REM SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\temp_executeas_sa.sql" -v DbName="tSQLt_dev" ExecuteStatement="EXEC tSQLt.SetVerbose @Verbose = 1;EXEC tSQLt.RunNew;" +SQLCMD -S .\R2A -E -I -d tSQLt_dev -i "..\Build\temp\temp_executeas_sa.sql" -v DbName="tSQLt_dev" ExecuteStatement="EXEC tSQLt.SetVerbose @Verbose = 1;EXEC tSQLt.Run '[EnableExternalAccessTests].[test tSQLt.EnableExternalAccess produces no output, if @try = 1 and setting fails]';" diff --git a/Experiments/SM query - read trace table.sql b/Experiments/SM query - read trace table.sql new file mode 100644 index 000000000..13d2094ca --- /dev/null +++ b/Experiments/SM query - read trace table.sql @@ -0,0 +1,61 @@ +SELECT + R.EventSequence, + TE.name, + TSV.subclass_name, + R.ObjectName, + R.LineNumber, + R.TransactionID, + R.XactSequence, + ISNULL(REPLICATE(' ',(R.NestLevel-1)),'')+CAST(R.TextData AS NVARCHAR(MAX)) AS TextData, + R.Error, + R.Severity, + R.NestLevel, + R.RowNumber, + R.EventClass, + R.EventSubClass, + R.ApplicationName, + R.ClientProcessID, + R.DatabaseID, + R.DatabaseName, + R.GroupID, + R.HostName, + R.IsSystem, + R.LoginName, + R.LoginSid, + R.NTDomainName, + R.NTUserName, + R.RequestID, + R.SPID, + R.ServerName, + R.SessionLoginName, + R.StartTime, + R.Success, + R.GUID, + R.BinaryData, + R.Duration, + R.EndTime, + R.IntegerData, + R.CPU, + R.IntegerData2, + R.Offset, + R.Reads, + R.RowCounts, + R.Writes, + R.State, + R.ObjectID, + R.ObjectType, + R.SourceDatabaseID, + R.IndexID, + R.Type, + R.Mode, + R.OwnerID, + R.ObjectID2, + R.BigintData1 + FROM dbo.run4 R + LEFT JOIN sys.trace_events AS TE + ON R.EventClass = TE.trace_event_id + LEFT JOIN sys.trace_subclass_values AS TSV + ON TSV.trace_event_id = TE.trace_event_id + AND R.EventSubClass = TSV.subclass_value + WHERE ISNULL(R.ObjectName,'???') NOT IN ('Private_Print','sp_rename', 'sp_validname','GetTestResultFormatter','sp_addextendedproperty','sp_updateextendedproperty') + ORDER BY R.EventSequence \ No newline at end of file diff --git a/Experiments/SQLCmdTest.1.1.sql b/Experiments/SQLCmdTest.1.1.sql new file mode 100644 index 000000000..45255732b --- /dev/null +++ b/Experiments/SQLCmdTest.1.1.sql @@ -0,0 +1 @@ +IF OBJECT_ID('dbo.testproc') IS NOT NULL DROP PROCEDURE dbo.testproc; diff --git a/Experiments/SQLCmdTest.1.2.sql b/Experiments/SQLCmdTest.1.2.sql new file mode 100644 index 000000000..cff82deab --- /dev/null +++ b/Experiments/SQLCmdTest.1.2.sql @@ -0,0 +1,5 @@ +CREATE PROC dbo.testproc +AS +BEGIN + RETURN +END; \ No newline at end of file diff --git a/Experiments/SQLCmdTest.1.sql b/Experiments/SQLCmdTest.1.sql new file mode 100644 index 000000000..97c68ac67 --- /dev/null +++ b/Experiments/SQLCmdTest.1.sql @@ -0,0 +1,10 @@ +:r C:\Data\git\tSQLt\tSQLt\Experiments\SQLCmdTest.1.1.sql + +:r C:\Data\git\tSQLt\tSQLt\Experiments\SQLCmdTest.1.2.sql + +GO + +:r C:\Data\git\tSQLt\tSQLt\Experiments\SQLCmdTest.1.1.sql +GO +:!! echo something +:r C:\Data\git\tSQLt\tSQLt\Experiments\SQLCmdTest.1.2.sql diff --git a/Experiments/TestingTransactionNames.sql b/Experiments/TestingTransactionNames.sql new file mode 100644 index 000000000..85958a011 --- /dev/null +++ b/Experiments/TestingTransactionNames.sql @@ -0,0 +1,21 @@ +IF(XACT_STATE()<>0)ROLLBACK +GO +DROP TABLE IF EXISTS #Actual +GO +DECLARE @TranName NVARCHAR(32) = NULL; +SELECT 1,@@TRANCOUNT +SELECT 1 X,@@TRANCOUNT TC INTO #Actual +BEGIN TRAN; +SELECT 2,@@TRANCOUNT +INSERT INTO #Actual VALUES(2,@@TRANCOUNT); +SAVE TRAN @TranName; +SELECT 3,@@TRANCOUNT +SELECT * FROM sys.dm_tran_current_transaction AS DTCT JOIN sys.dm_tran_session_transactions +AS DTAT ON DTAT.transaction_id = DTCT.transaction_id +INSERT INTO #Actual VALUES(3,@@TRANCOUNT); +SELECT * FROM #Actual AS A +ROLLBACK TRAN @TranName; +SELECT 4,@@TRANCOUNT +INSERT INTO #Actual VALUES(4,@@TRANCOUNT); +COMMIT; +SELECT * FROM #Actual AS A diff --git a/Experiments/UnprepareServer.sql b/Experiments/UnprepareServer.sql new file mode 100644 index 000000000..8c1b14642 --- /dev/null +++ b/Experiments/UnprepareServer.sql @@ -0,0 +1,29 @@ +GO +EXEC tSQLt.RemoveAssemblyKey; +GO +EXEC sp_configure @configname='clr enabled', @configvalue=0; +GO +RECONFIGURE +GO +EXEC sp_configure @configname='show adv', @configvalue=1; +GO +RECONFIGURE +GO +EXEC sp_configure @configname='clr stri', @configvalue=1; +GO +RECONFIGURE +GO +EXEC sp_configure @configname='show adv', @configvalue=0; +GO +RECONFIGURE +GO +SELECT * FROM master.sys.configurations AS C WHERE name LIKE '%clr%' +SELECT * FROM master.sys.server_principals AS SP WHERE SP.name LIKE '%tSQLt%'; +SELECT * FROM master.sys.asymmetric_keys AS AK WHERE AK.name LIKE '%tSQLt%'; +SELECT * FROM master.sys.trusted_assemblies AS TA --WHERE SP.name LIKE '%tSQLt%'; +SELECT * FROM master.sys.assemblies AS AK WHERE AK.name LIKE '%tSQLt%'; +SELECT name,AK.is_trustworthy_on FROM master.sys.databases AS AK WHERE AK.name LIKE '%tSQLt%'; +SELECT * FROM sys.server_permissions AS SP WHERE SP.grantee_principal_id = (SELECT SP2.principal_id FROM sys.server_principals AS SP2 WHERE SP2.name = 'tSQLtAssemblyKey'); +--||-- +--^^--Needs manual review for now +--||-- diff --git a/Experiments/WAITFOR_DELAY_Tests.sql b/Experiments/WAITFOR_DELAY_Tests.sql new file mode 100644 index 000000000..1552b60dd --- /dev/null +++ b/Experiments/WAITFOR_DELAY_Tests.sql @@ -0,0 +1,87 @@ +DROP TABLE IF EXISTS dbo.timelog; +GO +CREATE TABLE dbo.timelog( + id INT IDENTITY(1,1), + [@a] DATETIME2, + [@c] DATETIME2, + [@b] DATETIME2 +); +GO +SET NOCOUNT ON; +GO +CREATE PROCEDURE dbo.loopy + @loopcount INT = 100000 +AS +BEGIN + DECLARE @a DATETIME2, @b DATETIME2,@c DATETIME2; + WHILE(@loopcount > 0) + BEGIN + SET @a = SYSDATETIME(); + EXEC sp_executesql N'WAITFOR DELAY ''00:00:00.111'';SET @c = SYSDATETIME();',N'@c DATETIME2 OUTPUT', @c OUT; + SET @b = SYSDATETIME(); + INSERT INTO dbo.timelog + SELECT @a,@c,@b; + SET @loopcount -=1; + END; +END; +GO +EXEC dbo.loopy 1000000; +GO +SELECT [@a],[@c],[@b],DATEDIFF(MICROSECOND,[@a],[@b])[@b-@a],DATEDIFF(MICROSECOND,[@a],[@c])[@c-@a],DATEDIFF(MICROSECOND,[@c],[@b])[@b-@c] + FROM dbo.timelog AS T; +GO +-------------------------------------------------------------------------------------- +-------------------------------------------------------------------------------------- +-------------------------------------------------------------------------------------- +GO +SELECT * + FROM + ( + SELECT [@a],[@c],[@b],DATEDIFF(MILLISECOND,[@a],[@b])[@b-@a],DATEDIFF(MILLISECOND,[@a],[@c])[@c-@a],DATEDIFF(MILLISECOND,[@c],[@b])[@b-@c] + FROM dbo.timelog AS T WITH(NOLOCK) + )X + WHERE X.[@b-@a]<108000 OR X.[@b-@c]<>0 + + + GO + + SELECT + X.[@b-@a], + X.[@c-@a], + X.[@b-@c], + COUNT(1) + FROM + ( + SELECT [@a],[@c],[@b],DATEDIFF(MILLISECOND,[@a],[@b])[@b-@a],DATEDIFF(MILLISECOND,[@a],[@c])[@c-@a],DATEDIFF(MILLISECOND,[@c],[@b])[@b-@c] + FROM dbo.timelog AS T WITH(NOLOCK) + )X + GROUP BY GROUPING SETS ((X.[@b-@a], X.[@c-@a], X.[@b-@c]),()) + ORDER BY X.[@b-@a], X.[@c-@a], X.[@b-@c] + + +GO + + SELECT X.[@b-@a],COUNT(1) cnt FROM(SELECT [@a],[@c],[@b],DATEDIFF(MILLISECOND,[@a],[@b])[@b-@a] FROM dbo.timelog AS T WITH(NOLOCK))X GROUP BY GROUPING SETS ((X.[@b-@a]),())ORDER BY X.[@b-@a] + + + GO + SELECT oms, COUNT(1) cc + FROM( + SELECT RIGHT(ax,1) oms + FROM( + SELECT CONVERT(NVARCHAR(MAX),CAST([@a] AS DATETIME),121) ax FROM dbo.timelog + )x + )ox + GROUP BY ox.oms; + + -------------- +SELECT SYSDATETIME() +GO +DECLARE @dt TIME = DATEADD(MILLISECOND,111,SYSDATETIME()); +WHILE(CONVERT(NVARCHAR(12),SYSDATETIME(),121)<=CONVERT(NVARCHAR(12),@dt,121)) +BEGIN + WAITFOR DELAY '00:00:00.010'; +END; +GO +SELECT SYSDATETIME() +-------------- diff --git a/Experiments/WaitForTester.sql b/Experiments/WaitForTester.sql new file mode 100644 index 000000000..3eeb7ab7e --- /dev/null +++ b/Experiments/WaitForTester.sql @@ -0,0 +1,287 @@ +USE master; +GO +IF OBJECT_ID('tempdb..#ForceDropDatabaseAndLogin') IS NOT NULL DROP PROCEDURE #ForceDropDatabaseAndLogin; +GO +CREATE PROCEDURE #ForceDropDatabaseAndLogin +@db_name NVARCHAR(MAX) +AS +BEGIN + IF(DB_ID(@db_name)IS NOT NULL) + BEGIN + BEGIN TRY + EXEC(' + USE master; + ALTER DATABASE '+@db_name+' SET ONLINE WITH ROLLBACK IMMEDIATE; + ALTER DATABASE '+@db_name+' SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE; + USE '+@db_name+'; + ALTER DATABASE '+@db_name+' SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + '); + END TRY BEGIN CATCH END CATCH; + EXEC(' + USE master; + DROP DATABASE '+@db_name+'; + '); + IF SUSER_SID(@db_name) IS NOT NULL EXEC('DROP LOGIN '+@db_name+';'); + END; +END +GO +IF OBJECT_ID('tempdb..#CreateDatabaseWithOwningLogin') IS NOT NULL DROP PROCEDURE #CreateDatabaseWithOwningLogin; +GO +CREATE PROCEDURE #CreateDatabaseWithOwningLogin +@db_name NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX) = 'CREATE LOGIN '+@db_name+' WITH PASSWORD = '''; + SET @cmd = @cmd + CAST(NEWID() AS NVARCHAR(MAX))+''';'; + EXEC(@cmd); + EXEC('USE master;DENY CONNECT SQL TO '+@db_name+';'); + EXEC('CREATE DATABASE '+@db_name+';'); + EXEC('ALTER AUTHORIZATION ON DATABASE::'+@db_name+' TO '+@db_name+';'); + EXEC('ALTER DATABASE '+@db_name+' SET RECOVERY SIMPLE;'); +END +GO +IF OBJECT_ID('tempdb..#RecreateDB') IS NOT NULL DROP PROCEDURE #RecreateDB; +GO +CREATE PROCEDURE #RecreateDB +@db_name NVARCHAR(MAX) +AS +BEGIN + EXEC #ForceDropDatabaseAndLogin @db_name; + EXEC #CreateDatabaseWithOwningLogin @db_name; +END; +GO +EXEC #RecreateDB 'WaitForTester'; +GO +USE WaitForTester; +GO +IF OBJECT_ID('dbo.GetNums') IS NOT NULL DROP FUNCTION dbo.GetNums; +GO +CREATE FUNCTION dbo.GetNums(@n AS BIGINT) RETURNS TABLE +-- by Itzik Ben-Gan +-- http://www.sqlmag.com/article/sql-server/virtual-auxiliary-table-of-numbers +AS +RETURN + WITH + L0 AS(SELECT 1 AS c UNION ALL SELECT 1), + L1 AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B), + L2 AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B), + L3 AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B), + L4 AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B), + L5 AS(SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B), + Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L5) + SELECT TOP (@n) n FROM Nums ORDER BY n; +GO +--------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------- +GO +SET QUOTED_IDENTIFIER ON; +GO +DROP TABLE IF EXISTS dbo.timelog; +GO +CREATE TABLE dbo.timelog( + id INT IDENTITY(1,1), + [@a] DATETIME2, + [@c] DATETIME2, + [@b] DATETIME2, + spid INT DEFAULT @@SPID +); +GO +SET NOCOUNT ON; +GO +DROP PROCEDURE IF EXISTS dbo.loopy; +GO +CREATE PROCEDURE dbo.loopy + @loopcount INT = 100000 +AS +BEGIN + DECLARE @a DATETIME2, @b DATETIME2,@c DATETIME2; + WHILE(@loopcount > 0) + BEGIN + SET @a = SYSDATETIME(); + EXEC sp_executesql N'WAITFOR DELAY ''00:00:00.111'';SET @c = SYSDATETIME();',N'@c DATETIME2 OUTPUT', @c OUT; + SET @b = SYSDATETIME(); + INSERT INTO dbo.timelog([@a],[@c],[@b]) + SELECT @a,@c,@b; + SET @loopcount -=1; + END; +END; +GO +CREATE PROCEDURE dbo.WaitForMS + @milliseconds INT +AS +BEGIN + DECLARE @EndTime DATETIME2 = DATEADD(MILLISECOND,@milliseconds,SYSDATETIME()); + WHILE(SYSDATETIME()<@EndTime) + BEGIN + WAITFOR DELAY '00:00:00.010'; + END; +END; +GO +DROP PROCEDURE IF EXISTS dbo.loopyfix; +GO +CREATE PROCEDURE dbo.loopyfix + @loopcount INT = 100000 +AS +BEGIN + DECLARE @a DATETIME2, @b DATETIME2,@c DATETIME2; + WHILE(@loopcount > 0) + BEGIN + SET @a = SYSDATETIME(); + EXEC sp_executesql N'EXEC dbo.WaitForMS 120;SET @c = SYSDATETIME();',N'@c DATETIME2 OUTPUT', @c OUT; + SET @b = SYSDATETIME(); + INSERT INTO dbo.timelog([@a],[@c],[@b]) + SELECT @a,@c,@b; + SET @loopcount -=1; + END; +END; +GO +DROP PROCEDURE IF EXISTS dbo.loopynoop; +GO +CREATE PROCEDURE dbo.loopynoop + @loopcount INT = 100000 +AS +BEGIN + DECLARE @a DATETIME2, @b DATETIME2,@c DATETIME2; + WHILE(@loopcount > 0) + BEGIN + SET @a = SYSDATETIME(); + --EXEC sp_executesql N'EXEC dbo.WaitForMS 120;SET @c = SYSDATETIME();',N'@c DATETIME2 OUTPUT', @c OUT; + SET @b = SYSDATETIME(); + INSERT INTO dbo.timelog([@a],[@c],[@b]) + SELECT @a,@c,@b; + SET @loopcount -=1; + END; +END; +GO +--EXEC dbo.Loopy 10; +--SELECT X.[@b-@a],COUNT(1) cnt FROM(SELECT [@a],[@c],[@b],DATEDIFF(MILLISECOND,[@a],[@b])[@b-@a] FROM dbo.timelog AS T WITH(NOLOCK))X GROUP BY GROUPING SETS ((X.[@b-@a]),())ORDER BY X.[@b-@a]; +GO +--EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.Loopy 10;'; +GO +USE master; +GO +--------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------- +ALTER DATABASE WaitForTester SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE; +GO +USE WaitForTester; +--------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------- +GO +IF(EXISTS(SELECT 1 FROM sys.services WHERE name = '//AsyncExec/AsyncExecSourceQueue/Service'))DROP SERVICE [//AsyncExec/AsyncExecSourceQueue/Service]; +IF(OBJECT_ID('AsyncExec.AsyncExecSourceQueue') IS NOT NULL)DROP QUEUE AsyncExec.AsyncExecSourceQueue; +IF(EXISTS(SELECT 1 FROM sys.service_contracts WHERE name = '//AsyncExec/ExecuteCommand/Contract'))DROP CONTRACT [//AsyncExec/ExecuteCommand/Contract]; +IF(EXISTS(SELECT 1 FROM sys.service_message_types WHERE name = '//AsyncExec/ExecuteCommand'))DROP MESSAGE TYPE [//AsyncExec/ExecuteCommand]; +DROP PROCEDURE IF EXISTS AsyncExec.AsyncExecTargetQueueProcessor; +DROP PROCEDURE IF EXISTS AsyncExec.QueueCommand; +DROP SCHEMA IF EXISTS AsyncExec; +GO +CREATE SCHEMA AsyncExec; +GO +CREATE MESSAGE TYPE [//AsyncExec/ExecuteCommand] VALIDATION = WELL_FORMED_XML; +GO +CREATE CONTRACT [//AsyncExec/ExecuteCommand/Contract]([//AsyncExec/ExecuteCommand] SENT BY INITIATOR); +GO +CREATE QUEUE AsyncExec.AsyncExecSourceQueue WITH STATUS=ON, RETENTION=OFF; +GO +CREATE SERVICE [//AsyncExec/AsyncExecSourceQueue/Service] ON QUEUE AsyncExec.AsyncExecSourceQueue([//AsyncExec/ExecuteCommand/Contract]); +GO +CREATE PROCEDURE AsyncExec.QueueCommand + @cmd NVARCHAR(MAX) +AS +BEGIN + DECLARE @DialogHandle UNIQUEIDENTIFIER; + DECLARE @Message XML; + BEGIN TRANSACTION; + BEGIN DIALOG @DialogHandle + FROM SERVICE [//AsyncExec/AsyncExecSourceQueue/Service] + TO SERVICE N'//AsyncExec/AsyncExecSourceQueue/Service' + ON CONTRACT [//AsyncExec/ExecuteCommand/Contract] + WITH ENCRYPTION = OFF; + SELECT @Message = (SELECT @cmd [cmd] FOR XML PATH(''),TYPE); + SEND ON CONVERSATION @DialogHandle + MESSAGE TYPE [//AsyncExec/ExecuteCommand] + (@Message); + SELECT @Message AS QueuedCommand; + COMMIT TRANSACTION; +END; +GO +CREATE PROCEDURE AsyncExec.AsyncExecTargetQueueProcessor +AS +BEGIN + DECLARE @DialogHandle UNIQUEIDENTIFIER; + DECLARE @MessageTypeName sysname; + DECLARE @Message XML; + DECLARE @ErrMessage VARCHAR(MAX) = NULL; + DECLARE @cmd NVARCHAR(MAX); + WHILE (1=1) + BEGIN + BEGIN TRY + BEGIN TRANSACTION; + WAITFOR + ( + RECEIVE TOP(1) + @DialogHandle = conversation_handle, + @MessageTypeName = message_type_name, + @Message = CONVERT(XML, message_body) + FROM AsyncExec.AsyncExecSourceQueue + ), TIMEOUT 100000; + + IF (@@ROWCOUNT = 0) + BEGIN + IF (@@TRANCOUNT > 0 ) ROLLBACK TRANSACTION; + BREAK; + END; + + SET @cmd = NULL; + IF @MessageTypeName = N'//AsyncExec/ExecuteCommand' + BEGIN + SELECT @cmd = @Message.value('/cmd[1]','NVARCHAR(MAX)'); + END CONVERSATION @DialogHandle; + END; + COMMIT TRANSACTION; + END TRY + BEGIN CATCH + IF (@@TRANCOUNT > 0 ) ROLLBACK TRANSACTION; + SET @ErrMessage = 'Error during reading of cmd. (ErrorNumber=' + CONVERT(VARCHAR(100), ERROR_NUMBER()) + ' : ErrorMessage=' + ERROR_MESSAGE()+')'; + BREAK; + END CATCH; + + IF(@cmd IS NOT NULL) + BEGIN + BEGIN TRY + EXEC(@cmd); + END TRY + BEGIN CATCH + SET @ErrMessage = 'Error during execution of cmd (ErrorNumber=' + CONVERT(VARCHAR(100), ERROR_NUMBER()) + ' : ErrorMessage=' + ERROR_MESSAGE()+'): '+@cmd; + BREAK; + END CATCH; + END; + END; + IF(@ErrMessage IS NOT NULL) + BEGIN + RAISERROR(@ErrMessage, 16, 1); + END; +END; +GO +ALTER QUEUE AsyncExec.AsyncExecSourceQueue WITH ACTIVATION(STATUS=ON,MAX_QUEUE_READERS=20,PROCEDURE_NAME=AsyncExec.AsyncExecTargetQueueProcessor,EXECUTE AS OWNER); +GO +--------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------- +GO +EXEC AsyncExec.QueueCommand @cmd = 'EXEC dbo.loopynoop 1000000;'; +GO 20 +GO +SELECT * FROM AsyncExec.AsyncExecSourceQueue; +--SELECT * FROM sys.service_queues AS SQ +GO +--EXEC AsyncExec.AsyncExecTargetQueueProcessor; +SELECT X.[@b-@a],MIN([@a]) [MIN(@a)], MAX([@a])[MAX(@a)],COUNT(1) cnt FROM(SELECT [@a],[@c],[@b],DATEDIFF(MILLISECOND,[@a],[@b])[@b-@a] FROM dbo.timelog AS T WITH(NOLOCK))X GROUP BY GROUPING SETS ((X.[@b-@a]),())ORDER BY X.[@b-@a]; +GO +SELECT * FROM sys.dm_exec_requests +SELECT * FROM sys.dm_exec_sessions AS DES diff --git a/Experiments/sqlServerAlias.ps1 b/Experiments/sqlServerAlias.ps1 new file mode 100644 index 000000000..f17e97893 --- /dev/null +++ b/Experiments/sqlServerAlias.ps1 @@ -0,0 +1,15 @@ +# List existing aliases +Get-WmiObject -Namespace 'root\Microsoft\SqlServer\ComputerManagement15' -Class 'SqlServerAlias' | + Format-Table -Property 'AliasName', 'ServerName', 'ProtocolName', 'ConnectionString' + +# Delete existing aliases +$alias = Get-WmiObject -namespace 'root\Microsoft\SqlServer\ComputerManagement15' -class 'SqlServerAlias' -filter "AliasName='Dev_tSQLt'" +$alias.Delete() + +# Example script to create an alias +$alias = ([wmiclass] '\\.\root\Microsoft\SqlServer\ComputerManagement15:SqlServerAlias').CreateInstance() +$alias.AliasName = 'Dev_tSQLt' +$alias.ConnectionString = '41433' #connection specific parameters depending on the protocol +$alias.ProtocolName = 'tcp' +$alias.ServerName = '810D79BF7E9C' +$alias.Put() | Out-Null; \ No newline at end of file diff --git a/Experiments/sqlcmdErrorHandling.ps1 b/Experiments/sqlcmdErrorHandling.ps1 new file mode 100644 index 000000000..830fd6566 --- /dev/null +++ b/Experiments/sqlcmdErrorHandling.ps1 @@ -0,0 +1,25 @@ + +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ServerName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Login, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SqlCmdPath +); + + +$sqlFileForTesting = @" + RAISERROR('an error',15,2); + GO + RAISERROR('no error 1',0,1)WITH NOWAIT; + GO + RAISERROR('no error 2',0,1)WITH NOWAIT; + GO +"@; + +Set-Content "sqlFileForTesting.sql" $sqlFileForTesting; + +#$CallSqlCmd = '&"'+$SqlCmdPath+'\sqlcmd.exe" -S "'+$ServerName+'" '+$Login+' -I -b -Q "RAISERROR (''an error!'',15,3)"'; +$CallSqlCmd = '&"'+$SqlCmdPath+'\sqlcmd.exe" -S "'+$ServerName+'" '+$Login+' -I -b -i "sqlFileForTesting.sql"'; +$CallSqlCmd; +$CallSqlCmd = $CallSqlCmd + ';if($LASTEXITCODE -ne 0){throw "error during execution";}' +Invoke-Expression $CallSqlCmd; + diff --git a/Experiments/tSQLtListObjects.sql b/Experiments/tSQLtListObjects.sql new file mode 100644 index 000000000..93e80286d --- /dev/null +++ b/Experiments/tSQLtListObjects.sql @@ -0,0 +1,54 @@ +SELECT + O.type_desc, + QUOTENAME(S.name)+'.'+QUOTENAME(O.name) AS name, + PP.parameters, + CC.columns + FROM sys.objects AS O + JOIN sys.schemas AS S + ON S.schema_id = O.schema_id + CROSS APPLY + ( + SELECT SUBSTRING + ( + ( + SELECT ','+CHAR(13)+CHAR(10)+CASE WHEN P.parameter_id = 0 THEN '{RETURN}' ELSE P.name END+' '+ISNULL(PGFTN.TypeName,'') + FROM sys.parameters AS P + OUTER APPLY tSQLt.Private_GetFullTypeName(P.user_type_id,P.max_length,P.precision,P.scale,NULL) AS PGFTN + WHERE P.object_id = O.object_id + ORDER BY P.parameter_id + FOR XML PATH(''),TYPE + ).value('.','VARCHAR(MAX)'), + 4, + -1-POWER(-2,31) + ) parameters + )PP + CROSS APPLY + ( + SELECT SUBSTRING + ( + ( + SELECT ','+CHAR(13)+CHAR(10)+C.name+' '+ISNULL(PGFTN.TypeName,'') + FROM sys.columns AS C + OUTER APPLY tSQLt.Private_GetFullTypeName(C.user_type_id,C.max_length,C.precision,C.scale,C.collation_name) AS PGFTN + WHERE C.object_id = O.object_id + ORDER BY C.column_id + FOR XML PATH(''),TYPE + ).value('.','VARCHAR(MAX)'), + 4, + -1-POWER(-2,31) + ) columns + )CC + WHERE O.is_ms_shipped = 0 + AND S.name = 'tSQLt' + AND O.type IN('TT','FN','IF','U','FS','V','P','TF','PC','FT') + ORDER BY CASE WHEN O.name LIKE 'Private[_]%' THEN 1 ELSE 0 END,O.type_desc,name + + +SELECT SCHEMA_NAME(T.schema_id),* FROM sys.types AS T +--SELECT * FROM sys.columns AS C + +--SELECT type,O.type_desc,COUNT(1) FROM sys.objects AS O GROUP BY O.type ,O.type_desc + +--SELECT -1-POWER(-2,31) + +--SELECT CAST(', ' AS VARBINARY(MAX)) diff --git a/Experiments/tSQLtOfficialSigningKey.pfx b/Experiments/tSQLtOfficialSigningKey.pfx new file mode 100644 index 000000000..8990acbf9 Binary files /dev/null and b/Experiments/tSQLtOfficialSigningKey.pfx differ diff --git a/PADGen/sqlitynet/pad_tsqlt.xml b/PADGen/sqlitynet/pad_tsqlt.xml deleted file mode 100644 index de2d581b3..000000000 --- a/PADGen/sqlitynet/pad_tsqlt.xml +++ /dev/null @@ -1,216 +0,0 @@ - - - - 3.11 - PADGen 3.1.1.47 http://www.padgen.org - Portable Application Description, or PAD for short, is a data set that is used by shareware authors to disseminate information to anyone interested in their software products. To find out more go to http://pad.asp-software.org - - - sqlity.net - - - Malvern - PA - - USA - http://sqlity.net - - tSQLt - sqlity.net - tsqlt@sqlity.net - tsqlt - sqlity - tsqlt@sqlity.net - - - tsqlt@googlegroups.com - tsqlt@googlegroups.com - tsqlt@googlegroups.com - - - - - - - - tSQLt - 7 - 05 - 23 - 2010 - - - - Freeware - New Release - - Not Applicable - English - - Development Tools - Development::Other - MS SQL Server 2005+ - - 17500 - 17 - 0.02 - - - N - - Days - - - - - - - - - t-sql, unit testing, SQL-Server, TDD, Database TDD, Test Driven Development, SQL - t-sql unit testing framework - t-sql unit testing framework with support for dependency isolation - tSQLt provides the ability to write unit tests for Microsoft SQL Server T-SQL code. Unlike other frameworks it allows the developer to isolate the unit being tested. For example, it can fake tables or views, spy procedures and test constraints. - tSQLt provides the ability to write unit tests for Microsoft SQL Server T-SQL code. Unlike other frameworks it allows the developer to isolate the unit being tested. For example, it can fake tables or views, spy procedures and test constraints. - - What is tSQLt? -tSQLt is a unit testing framework for Microsoft SQL Server. tSQLt is compatible with SQL Server 2005 and above on all editions. - -- Main Features -tSQLt allows you to implement unit tests in T-SQL. This is important as you do not have to switch between various tools to create your code and your unit tests. tSQLt also provides the following features to make it easier to create and manage unit tests: -Tests are automatically run within transactions - this keeps tests independent and reduces any cleanup work you need -Tests can be grouped together within a schema - allowing you to organize your tests and use common setup methods -Output can be generated in plain text or XML - making it easier to integrate with a continuous integration tool -Provides the ability to fake tables and views, and to create stored procedure spies - allowing you to isolate the code which you are testing - - - - - http://tsqlt.org - - - - - - - http://sourceforge.net/projects/tsqlt/ - - - - - - - - - - - Y - 1.4 - http://pad.asp-software.org/extensions/Affiliates.htm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Y - N - - - diff --git a/PADGen/sqlitynet/sqlitynet.cml b/PADGen/sqlitynet/sqlitynet.cml deleted file mode 100644 index 3a728340a..000000000 --- a/PADGen/sqlitynet/sqlitynet.cml +++ /dev/null @@ -1,31 +0,0 @@ - - - PADGen 3.1.1.47 - - sqlity.net - - - Malvern - PA - - USA - http://sqlity.net - - tSQLt - sqlity.net - tsqlt@sqlity.net - tsqlt - sqlity - tsqlt@sqlity.net - - - tsqlt@googlegroups.com - tsqlt@googlegroups.com - tsqlt@googlegroups.com - - - - - - - diff --git a/PADGen/sqlitynet/tsqlt.pml b/PADGen/sqlitynet/tsqlt.pml deleted file mode 100644 index 3c0dd284f..000000000 --- a/PADGen/sqlitynet/tsqlt.pml +++ /dev/null @@ -1,186 +0,0 @@ - - - PADGen 3.1.1.47 - - - tSQLt - 7 - 05 - 23 - 2010 - - - - Freeware - New Release - - Not Applicable - English - - Development Tools - Development::Other - MS SQL Server 2005+ - - 17500 - 17 - 0.02 - - - N - - Days - - - - - - - - - t-sql, unit testing, SQL-Server, TDD, Database TDD, Test Driven Development, SQL - t-sql unit testing framework - t-sql unit testing framework with support for dependency isolation - tSQLt provides the ability to write unit tests for Microsoft SQL Server T-SQL code. Unlike other frameworks it allows the developer to isolate the unit being tested. For example, it can fake tables or views, spy procedures and test constraints. - tSQLt provides the ability to write unit tests for Microsoft SQL Server T-SQL code. Unlike other frameworks it allows the developer to isolate the unit being tested. For example, it can fake tables or views, spy procedures and test constraints. - - What is tSQLt? -tSQLt is a unit testing framework for Microsoft SQL Server. tSQLt is compatible with SQL Server 2005 and above on all editions. - -- Main Features -tSQLt allows you to implement unit tests in T-SQL. This is important as you do not have to switch between various tools to create your code and your unit tests. tSQLt also provides the following features to make it easier to create and manage unit tests: -Tests are automatically run within transactions - this keeps tests independent and reduces any cleanup work you need -Tests can be grouped together within a schema - allowing you to organize your tests and use common setup methods -Output can be generated in plain text or XML - making it easier to integrate with a continuous integration tool -Provides the ability to fake tables and views, and to create stored procedure spies - allowing you to isolate the code which you are testing - - - - - http://tsqlt.org - - - - - - - http://sourceforge.net/projects/tsqlt/ - - - - - - - - - - - Y - 1.4 - http://pad.asp-software.org/extensions/Affiliates.htm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Y - N - - - diff --git a/README.md b/README.md index c0f45a72b..cd477cb9f 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,6 @@ tSQLt allows you to implement unit tests in T-SQL. This is important as you do n * Tests can be grouped together within a schema – allowing you to organize your tests and use common setup methods * Output can be generated in plain text or XML – making it easier to integrate with a continuous integration tool -tSQLt is licensed under Apache 2.0 (https://github.com/tSQLt-org/tSQLt/blob/master/Build/License.txt) \ No newline at end of file +tSQLt is licensed under Apache 2.0 (https://github.com/tSQLt-org/tSQLt/blob/master/Build/License.txt) + + diff --git a/Source/ApplyConstraint_Methods.sql b/Source/ApplyConstraint_Methods.sql index c406deb5f..180cfc875 100644 --- a/Source/ApplyConstraint_Methods.sql +++ b/Source/ApplyConstraint_Methods.sql @@ -23,7 +23,7 @@ RETURN ON newtbl.object_id = p.major_id AND p.minor_id = 0 AND p.class_desc = 'OBJECT_OR_COLUMN' - AND p.name = 'tSQLt.FakeTable_OrgTableName' + AND p.name = 'tSQLt.Private_TestDouble_OrgObjectName' ON OBJECT_NAME(constraints.parent_object_id) = CAST(p.value AS NVARCHAR(4000)) AND constraints.schema_id = newtbl.schema_id AND constraints.object_id = @ConstraintObjectId; @@ -70,6 +70,8 @@ CREATE PROCEDURE tSQLt.Private_ApplyCheckConstraint AS BEGIN DECLARE @Cmd NVARCHAR(MAX); + DECLARE @NewNameOfOriginalConstraint NVARCHAR(MAX); + DECLARE @QuotedFullConstraintName NVARCHAR(MAX); SELECT @Cmd = 'CONSTRAINT ' + QUOTENAME(name) + ' CHECK' + definition FROM sys.check_constraints WHERE object_id = @ConstraintObjectId; @@ -78,13 +80,16 @@ BEGIN SELECT @QuotedTableName = QuotedTableName FROM tSQLt.Private_GetQuotedTableNameForConstraint(@ConstraintObjectId); - EXEC tSQLt.Private_RenameObjectToUniqueNameUsingObjectId @ConstraintObjectId; - SELECT @Cmd = 'ALTER TABLE ' + @QuotedTableName + ' ADD ' + @Cmd + SELECT @Cmd = 'ALTER TABLE ' + @QuotedTableName + ' ADD ' + @Cmd, + @QuotedFullConstraintName = QUOTENAME(SCHEMA_NAME(schema_id))+'.'+QUOTENAME(name) FROM sys.objects WHERE object_id = @ConstraintObjectId; + EXEC tSQLt.Private_RenameObjectToUniqueNameUsingObjectId @ConstraintObjectId, @NewName = @NewNameOfOriginalConstraint OUT; + EXEC (@Cmd); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = @QuotedFullConstraintName, @ObjectType = 'CONSTRAINT', @NewNameOfOriginalObject = @NewNameOfOriginalConstraint; END; GO @@ -101,11 +106,15 @@ BEGIN DECLARE @AlterTableCmd NVARCHAR(MAX); DECLARE @CreateIndexCmd NVARCHAR(MAX); DECLARE @FinalCmd NVARCHAR(MAX); + DECLARE @NewNameOfOriginalConstraint NVARCHAR(MAX); + DECLARE @QuotedFullConstraintName NVARCHAR(MAX); + SELECT @SchemaName = SchemaName, @OrgTableName = OrgTableName, @TableName = TableName, - @ConstraintName = OBJECT_NAME(@ConstraintObjectId) + @ConstraintName = OBJECT_NAME(@ConstraintObjectId), + @QuotedFullConstraintName = QUOTENAME(SchemaName)+'.'+QUOTENAME(OBJECT_NAME(@ConstraintObjectId)) FROM tSQLt.Private_GetQuotedTableNameForConstraint(@ConstraintObjectId); SELECT @CreateFkCmd = cmd, @CreateIndexCmd = CreIdxCmd @@ -114,8 +123,11 @@ BEGIN ' ADD ' + @CreateFkCmd; SELECT @FinalCmd = @CreateIndexCmd + @AlterTableCmd; - EXEC tSQLt.Private_RenameObjectToUniqueName @SchemaName, @ConstraintName; + EXEC tSQLt.Private_RenameObjectToUniqueName @SchemaName, @ConstraintName, @NewName = @NewNameOfOriginalConstraint OUTPUT; EXEC (@FinalCmd); + + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = @QuotedFullConstraintName, @ObjectType = 'CONSTRAINT', @NewNameOfOriginalObject = @NewNameOfOriginalConstraint; + END; GO @@ -124,25 +136,28 @@ CREATE PROCEDURE tSQLt.Private_ApplyUniqueConstraint AS BEGIN DECLARE @SchemaName NVARCHAR(MAX); - DECLARE @OrgTableName NVARCHAR(MAX); DECLARE @TableName NVARCHAR(MAX); DECLARE @ConstraintName NVARCHAR(MAX); DECLARE @CreateConstraintCmd NVARCHAR(MAX); DECLARE @AlterColumnsCmd NVARCHAR(MAX); + DECLARE @NewNameOfOriginalConstraint NVARCHAR(MAX); + DECLARE @QuotedFullConstraintName NVARCHAR(MAX); SELECT @SchemaName = SchemaName, - @OrgTableName = OrgTableName, @TableName = TableName, - @ConstraintName = OBJECT_NAME(@ConstraintObjectId) + @ConstraintName = OBJECT_NAME(@ConstraintObjectId), + @QuotedFullConstraintName = QUOTENAME(SchemaName)+'.'+QUOTENAME(OBJECT_NAME(@ConstraintObjectId)) FROM tSQLt.Private_GetQuotedTableNameForConstraint(@ConstraintObjectId); SELECT @AlterColumnsCmd = NotNullColumnCmd, @CreateConstraintCmd = CreateConstraintCmd FROM tSQLt.Private_GetUniqueConstraintDefinition(@ConstraintObjectId, QUOTENAME(@SchemaName) + '.' + QUOTENAME(@TableName)); - EXEC tSQLt.Private_RenameObjectToUniqueName @SchemaName, @ConstraintName; + EXEC tSQLt.Private_RenameObjectToUniqueName @SchemaName, @ConstraintName, @NewName = @NewNameOfOriginalConstraint OUTPUT; EXEC (@AlterColumnsCmd); EXEC (@CreateConstraintCmd); + + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = @QuotedFullConstraintName, @ObjectType = 'CONSTRAINT', @NewNameOfOriginalObject = @NewNameOfOriginalConstraint; END; GO diff --git a/Source/BuildOrder.txt b/Source/BuildOrder.txt index fb97a1066..438bff8a6 100644 --- a/Source/BuildOrder.txt +++ b/Source/BuildOrder.txt @@ -1,19 +1,29 @@ tSQLt._Header.sql +../Build/temp/tSQLtBuild/TempDropClass.sql +tSQLt.schema.sql +tSQLt.TestClass.user.sql +tSQLt.Private_GetDropItemCmd.sfn.sql +tSQLt.Private_Results.view.sql tSQLt.DropClass.ssp.sql -tSQLt.Private_Bin2Hex.sfn.sql +tSQLt.Uninstall.ssp.sql +tSQLt.TestClasses.view.sql +tSQLt.Tests.view.sql +tSQLt.Private_HostPlatform.stub.sql +tSQLt.Private_InstallationInfo.stub.sql +../Build/temp/tSQLtBuild/tSQLt.Private_GetAssemblyKeyBytes.sql +tSQLt.Private_GetSQLProductMajorVersion.ssp.sql +tSQLt.Private_EnableCLR.ssp.sql +tSQLt.RemoveAssemblyKey.ssp.sql +tSQLt.InstallAssemblyKey.ssp.sql +tSQLt.PrepareServer.ssp.sql tSQLt.Private_NewTestClassList.tbl.sql tSQLt.Private_ResetNewTestClassList.ssp.sql -tSQLt.Private_SysTypes.svw.sql -tSQLt.Private_GetFullTypeName.sfn.sql tSQLt.Private_DisallowOverwritingNonTestSchema.ssp.sql tSQLt.Private_QuoteClassNameForNewTestClass.sfn.sql tSQLt.Private_MarkSchemaAsTestClass.ssp.sql tSQLt.NewTestClass.ssp.sql tSQLt.Fail.ssp.sql tSQLt.class.sql -../Build/temp/tSQLt.Private_GetExternalAccessKeyBytes.sql -tSQLt.RemoveExternalAccessKey.ssp.sql -tSQLt.InstallExternalAccessKey.ssp.sql tSQLt.EnableExternalAccess.ssp.sql tSQLt.Private_Configurations.tbl.sql tSQLt.Private_SetConfiguration.ssp.sql @@ -21,22 +31,48 @@ tSQLt.Private_GetConfiguration.sfn.sql tSQLt.SetVerbose.ssp.sql tSQLt.CaptureOutputLog.tbl.sql tSQLt.LogCapturedOutput.ssp.sql -../Build/temp/CreateAssembly.sql +../Build/temp/tSQLtBuild/CreateAssembly.sql tSQLtCLR_CreateProcs.sql +tSQLt.Private_PrepareFakeFunctionOutputTable.ssp.sql +tSQLt.Private_TableToTextColumnListAdjustWidth.sfn.sql +tSQLt.Private_TableToTextColumnListMaxLenNumbered.sfn.sql +tSQLt.Private_TableToTextColumnListQuotedAndNumbered.sfn.sql +tSQLt.Private_TableToTextColumnListSeparatorLine.sfn.sql +tSQLt.Private_TableToTextColumntoStringList.sfn.sql +tSQLt.Private_TableToTextNumberedColumnsWithSeparator.sfn.sql tSQLt.TableToText.ssp.sql tSQLt.Private_RenamedObjectLog.tbl.sql +tSQLt.Private_RenameObject.ssp.sql tSQLt.Private_MarkObjectBeforeRename.ssp.sql tSQLt.Private_RenameObjectToUniqueName.ssp.sql tSQLt.Private_RenameObjectToUniqueNameUsingObjectId.ssp.sql tSQLt.RemoveObject.ssp.sql tSQLt.RemoveObjectIfExists.ssp.sql +tSQLt.Private_GetFormattedErrorInfo.sfn.sql +tSQLt.Private_HandleMessageAndResult.sfn.sql tSQLt.Private_CleanTestResult.ssp.sql -tSQLt.Private_Init.ssp.sql -Run_Methods.sql +tSQLt.Private_ListTestAnnotations.sfn.sql +tSQLt.Private_ProcessTestAnnotations.ssp.sql tSQLt.ExpectException.ssp.sql tSQLt.ExpectNoException.ssp.sql -tSQLt.private_SqlVersion.sfn.sql +tSQLt.Private_SqlVersion.sfn.sql +tSQLt.Private_SplitSqlVersion.sfn.sql +tSQLt.FriendlySQLServerVersion.sfn.sql tSQLt.Info.sfn.sql +tSQLt.Private_Seize.tbl.sql +tSQLt.Private_Init.ssp.sql +tSQLt.Private_HostPlatform.svw.sql +tSQLt.Private_NoTransactionTableAction.view.sql +tSQLt.Private_MarktSQLtTempObject.ssp.sql +tSQLt.Private_NoTransactionHandleTable.ssp.sql +tSQLt.Private_NoTransactionHandleTables.ssp.sql +tSQLt.Private_CleanUpCmdHandler.ssp.sql +tSQLt.Private_CleanUp.ssp.sql +tSQLt.Private_AssertNoSideEffects_GenerateCommand.sfn.sql +tSQLt.Private_AssertNoSideEffects.ssp.sql +Run_Methods.sql +tSQLt.Private_SysTypes.svw.sql +tSQLt.Private_GetFullTypeName.sfn.sql tSQLt.Private_SysIndexes.svw.sql tSQLt.Private_ScriptIndex.sfn.sql tSQLt.Private_RemoveSchemaBinding.ssp.sql @@ -48,17 +84,18 @@ tSQLt.Private_GetDataTypeOrComputedColumnDefinition.sfn.sql tSQLt.Private_GetIdentityDefinition.sfn.sql tSQLt.Private_GetDefaultConstraintDefinition.sfn.sql tSQLt.Private_GetUniqueConstraintDefinition.sfn.sql +tSQLt.Private_CreateFakeTableStatement.sfn.sql tSQLt.Private_CreateFakeOfTable.ssp.sql -tSQLt.Private_MarkFakeTable.ssp.sql tSQLt.FakeTable.ssp.sql +tSQLt.Private_GenerateCreateProcedureSpyStatement.ssp.sql tSQLt.Private_CreateProcedureSpy.ssp.sql +tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure.ssp.sql tSQLt.SpyProcedure.ssp.sql tSQLt.Private_GetCommaSeparatedColumnList.sfn.sql tSQLt.Private_CreateResultTableForCompareTables.ssp.sql tSQLt.Private_ValidateThatAllDataTypesInTableAreSupported.ssp.sql tSQLt.Private_CompareTablesFailIfUnequalRowsExists.ssp.sql tSQLt.Private_CompareTables.ssp.sql -tSQLt.Private_NullCellTable.tbl.sql tSQLt.AssertObjectExists.ssp.sql tSQLt.AssertObjectDoesNotExist.ssp.sql tSQLt.AssertEqualsString.ssp.sql @@ -69,13 +106,25 @@ tSQLt.AssertNotEquals.ssp.sql tSQLt.Private_SqlVariantFormatter.sfn.sql tSQLt.AssertEmptyTable.ssp.sql tSQLt.ApplyTrigger.ssp.sql +tSQLt.Private_CreateFakeFunctionStatement.sfn.sql tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction.ssp.sql tSQLt.Private_CreateFakeFunction.ssp.sql tSQLt.FakeFunction.ssp.sql tSQLt.RenameClass.ssp.sql -tSQLt.AssertEqualsTableSchema.tables.sql tSQLt.AssertEqualsTableSchema.ssp.sql tSQLt.AssertStringTable.udt.sql tSQLt.AssertStringIn.ssp.sql +tSQLt.SetSummaryError.ssp.sql tSQLt.Reset.ssp.sql -tSQLt._Footer.sql +tSQLt.Private_SkipTestAnnotationHelper.ssp.sql +tSQLt.(at)tSQLt_SkipTest.sfn.sql +tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql +tSQLt.(at)tSQLt_MaxSqlMajorVersion.sfn.sql +tSQLt.(at)tSQLt_RunOnlyOnHostPlatform.sfn.sql +tSQLt.(at)tSQLt_NoTransaction.sfn.sql +tSQLt.RemoveExternalAccessKey.ssp.sql +tSQLt.InstallExternalAccessKey.ssp.sql +tSQLt.Private_InstallationInfo.sfn.sql +tSQLt.Private_UndoSingleTestDouble.ssp.sql +tSQLt.UndoTestDoubles.ssp.sql +tSQLt._Footer.sql \ No newline at end of file diff --git a/Source/ExecutePrepareServer.sql b/Source/ExecutePrepareServer.sql new file mode 100644 index 000000000..b89d9f8ce --- /dev/null +++ b/Source/ExecutePrepareServer.sql @@ -0,0 +1 @@ +EXEC tSQLt.PrepareServer; diff --git a/Source/PrepareServerBuildOrder.txt b/Source/PrepareServerBuildOrder.txt new file mode 100644 index 000000000..8fcc0f403 --- /dev/null +++ b/Source/PrepareServerBuildOrder.txt @@ -0,0 +1,7 @@ +../Build/temp/tSQLtBuild/tSQLt.Private_GetAssemblyKeyBytes.sql +tSQLt.Private_EnableCLR.ssp.sql +tSQLt.Private_GetSQLProductMajorVersion.ssp.sql +tSQLt.RemoveAssemblyKey.ssp.sql +tSQLt.InstallAssemblyKey.ssp.sql +tSQLt.PrepareServer.ssp.sql +ExecutePrepareServer.sql diff --git a/Source/ResetValidationServerBuildOrder1.txt b/Source/ResetValidationServerBuildOrder1.txt new file mode 100644 index 000000000..f42378a65 --- /dev/null +++ b/Source/ResetValidationServerBuildOrder1.txt @@ -0,0 +1,4 @@ +../Build/temp/tSQLtTests/Source/tSQLt.Private_GetAssemblyKeyBytes.sql +tSQLt.Private_GetSQLProductMajorVersion.ssp.sql +tSQLt.RemoveAssemblyKey.ssp.sql +../Build/SQL/ExecuteResetValidationServer.sql diff --git a/Source/ResetValidationServerBuildOrder2.txt b/Source/ResetValidationServerBuildOrder2.txt new file mode 100644 index 000000000..907d67995 --- /dev/null +++ b/Source/ResetValidationServerBuildOrder2.txt @@ -0,0 +1,3 @@ +../Build/temp/tSQLtTests/ResetValidationServer.tmp.sql +../Build/SQL/ForceDropTempProcedures.sql +../Build/SQL/CreateLogin(tSQLt.Build).sql \ No newline at end of file diff --git a/Source/Run_Methods.sql b/Source/Run_Methods.sql index f11f1b489..2395b8896 100644 --- a/Source/Run_Methods.sql +++ b/Source/Run_Methods.sql @@ -1,5 +1,6 @@ -IF OBJECT_ID('tSQLt.Private_GetSetupProcedureName') IS NOT NULL DROP PROCEDURE tSQLt.Private_GetSetupProcedureName; +IF OBJECT_ID('tSQLt.Private_GetClassHelperProcedureName') IS NOT NULL DROP PROCEDURE tSQLt.Private_GetClassHelperProcedureName; IF OBJECT_ID('tSQLt.Private_RunTest') IS NOT NULL DROP PROCEDURE tSQLt.Private_RunTest; +IF OBJECT_ID('tSQLt.Private_RunTest_TestExecution') IS NOT NULL DROP PROCEDURE tSQLt.Private_RunTest_TestExecution; IF OBJECT_ID('tSQLt.Private_RunTestClass') IS NOT NULL DROP PROCEDURE tSQLt.Private_RunTestClass; IF OBJECT_ID('tSQLt.Private_Run') IS NOT NULL DROP PROCEDURE tSQLt.Private_Run; IF OBJECT_ID('tSQLt.Private_RunCursor') IS NOT NULL DROP PROCEDURE tSQLt.Private_RunCursor; @@ -20,42 +21,262 @@ IF OBJECT_ID('tSQLt.DefaultResultFormatter') IS NOT NULL DROP PROCEDURE tSQLt.De IF OBJECT_ID('tSQLt.XmlResultFormatter') IS NOT NULL DROP PROCEDURE tSQLt.XmlResultFormatter; IF OBJECT_ID('tSQLt.NullTestResultFormatter') IS NOT NULL DROP PROCEDURE tSQLt.NullTestResultFormatter; IF OBJECT_ID('tSQLt.RunTestClass') IS NOT NULL DROP PROCEDURE tSQLt.RunTestClass; +IF OBJECT_ID('tSQLt.Private_PrepareTestResultForOutput') IS NOT NULL DROP FUNCTION tSQLt.Private_PrepareTestResultForOutput; GO ---Build+ -CREATE PROCEDURE tSQLt.Private_GetSetupProcedureName +CREATE PROCEDURE tSQLt.Private_GetClassHelperProcedureName @TestClassId INT = NULL, - @SetupProcName NVARCHAR(MAX) OUTPUT + @SetupProcName NVARCHAR(MAX) OUTPUT, + @CleanUpProcName NVARCHAR(MAX) OUTPUT AS BEGIN SELECT @SetupProcName = tSQLt.Private_GetQuotedFullName(object_id) FROM sys.procedures WHERE schema_id = @TestClassId AND LOWER(name) = 'setup'; + SELECT @CleanUpProcName = tSQLt.Private_GetQuotedFullName(object_id) + FROM sys.procedures + WHERE schema_id = @TestClassId + AND LOWER(name) = 'cleanup'; +END; +GO + +CREATE PROCEDURE tSQLt.Private_RunTest_TestExecution + @TestName NVARCHAR(MAX), + @SetUp NVARCHAR(MAX), + @CleanUp NVARCHAR(MAX), + @NoTransactionFlag BIT, + @TranName CHAR(32), + @Result NVARCHAR(MAX) OUTPUT, + @Msg NVARCHAR(MAX) OUTPUT, + @TestEndTime DATETIME2 OUTPUT +AS +BEGIN + DECLARE @TransactionStartedFlag BIT = 0; + DECLARE @PreExecTrancount INT = NULL; + DECLARE @TestExecutionCmd NVARCHAR(MAX) = 'EXEC ' + @TestName; + DECLARE @CleanUpProcedureExecutionCmd NVARCHAR(MAX) = NULL; + + BEGIN TRY + + IF(@NoTransactionFlag = 0) + BEGIN + BEGIN TRAN; + SET @TransactionStartedFlag = 1; + SAVE TRAN @TranName; + END; + ELSE + BEGIN + SELECT object_id ObjectId, SCHEMA_NAME(schema_id) SchemaName, name ObjectName, type_desc ObjectType INTO #BeforeExecutionObjectSnapshot FROM sys.objects; + EXEC tSQLt.Private_NoTransactionHandleTables @Action = 'Save'; + END; + + SET @PreExecTrancount = @@TRANCOUNT; + + DECLARE @TmpMsg NVARCHAR(MAX); + SET @TestEndTime = NULL; + BEGIN TRY + IF (@SetUp IS NOT NULL) + BEGIN + EXEC @SetUp; + END; + + EXEC (@TestExecutionCmd); + + IF(EXISTS(SELECT 1 FROM #ExpectException WHERE ExpectException = 1)) + BEGIN + SET @TmpMsg = COALESCE((SELECT FailMessage FROM #ExpectException)+' ','')+'Expected an error to be raised.'; + EXEC tSQLt.Fail @TmpMsg; + END + SET @TestEndTime = SYSDATETIME(); + END TRY + BEGIN CATCH + SET @TestEndTime = ISNULL(@TestEndTime,SYSDATETIME()); + IF ERROR_MESSAGE() LIKE '%tSQLt.Failure%' + BEGIN + SELECT @Msg = Msg FROM #TestMessage; + SET @Result = 'Failure'; + END + ELSE + BEGIN + DECLARE @ErrorInfo NVARCHAR(MAX); + SELECT @ErrorInfo = FormattedError FROM tSQLt.Private_GetFormattedErrorInfo(); + + IF(EXISTS(SELECT 1 FROM #ExpectException)) + BEGIN + DECLARE @ExpectException INT; + DECLARE @ExpectedMessage NVARCHAR(MAX); + DECLARE @ExpectedMessagePattern NVARCHAR(MAX); + DECLARE @ExpectedSeverity INT; + DECLARE @ExpectedState INT; + DECLARE @ExpectedErrorNumber INT; + DECLARE @FailMessage NVARCHAR(MAX); + SELECT @ExpectException = ExpectException, + @ExpectedMessage = ExpectedMessage, + @ExpectedSeverity = ExpectedSeverity, + @ExpectedState = ExpectedState, + @ExpectedMessagePattern = ExpectedMessagePattern, + @ExpectedErrorNumber = ExpectedErrorNumber, + @FailMessage = FailMessage + FROM #ExpectException; + + IF(@ExpectException = 1) + BEGIN + SET @Result = 'Success'; + SET @TmpMsg = COALESCE(@FailMessage+' ','')+'Exception did not match expectation!'; + IF(ERROR_MESSAGE() <> @ExpectedMessage) + BEGIN + SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ + 'Expected Message: <'+@ExpectedMessage+'>'+CHAR(13)+CHAR(10)+ + 'Actual Message : <'+ERROR_MESSAGE()+'>'; + SET @Result = 'Failure'; + END + IF(ERROR_MESSAGE() NOT LIKE @ExpectedMessagePattern) + BEGIN + SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ + 'Expected Message to be like <'+@ExpectedMessagePattern+'>'+CHAR(13)+CHAR(10)+ + 'Actual Message : <'+ERROR_MESSAGE()+'>'; + SET @Result = 'Failure'; + END + IF(ERROR_NUMBER() <> @ExpectedErrorNumber) + BEGIN + SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ + 'Expected Error Number: '+CAST(@ExpectedErrorNumber AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+ + 'Actual Error Number : '+CAST(ERROR_NUMBER() AS NVARCHAR(MAX)); + SET @Result = 'Failure'; + END + IF(ERROR_SEVERITY() <> @ExpectedSeverity) + BEGIN + SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ + 'Expected Severity: '+CAST(@ExpectedSeverity AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+ + 'Actual Severity : '+CAST(ERROR_SEVERITY() AS NVARCHAR(MAX)); + SET @Result = 'Failure'; + END + IF(ERROR_STATE() <> @ExpectedState) + BEGIN + SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ + 'Expected State: '+CAST(@ExpectedState AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+ + 'Actual State : '+CAST(ERROR_STATE() AS NVARCHAR(MAX)); + SET @Result = 'Failure'; + END + IF(@Result = 'Failure') + BEGIN + SET @Msg = @TmpMsg; + END + END + ELSE + BEGIN + SET @Result = 'Failure'; + SET @Msg = + COALESCE(@FailMessage+' ','')+ + 'Expected no error to be raised. Instead this error was encountered:'+ + CHAR(13)+CHAR(10)+ + @ErrorInfo; + END + END; + ELSE + BEGIN + SET @Result = 'Error'; + SET @Msg = @ErrorInfo; + END; + END; + END CATCH; + END TRY + BEGIN CATCH + SET @Result = 'Error'; + SET @Msg = ERROR_MESSAGE(); + END CATCH + + --TODO:NoTran + ---- Compare @@Trancount, throw up arms if it doesn't match + --TODO:NoTran + BEGIN TRY + IF(@TransactionStartedFlag = 1) + BEGIN + ROLLBACK TRAN @TranName; + END; + END TRY + BEGIN CATCH + DECLARE @PostExecTrancount INT; + SET @PostExecTrancount = @PreExecTrancount - @@TRANCOUNT; + IF (@@TRANCOUNT > 0) ROLLBACK; + BEGIN TRAN; + IF( @Result <> 'Success' + OR @PostExecTrancount <> 0 + ) + BEGIN + SELECT @Msg = COALESCE(@Msg, '') + ' (There was also a ROLLBACK ERROR --> ' + FormattedError + ')' FROM tSQLt.Private_GetFormattedErrorInfo(); + SET @Result = 'Error'; + END; + END CATCH; + IF (@NoTransactionFlag = 1) + BEGIN + SET @CleanUpProcedureExecutionCmd = ( + ( + SELECT 'EXEC tSQLt.Private_CleanUpCmdHandler ''EXEC '+ REPLACE(NT.CleanUpProcedureName,'''','''''') +';'', @Result OUT, @Msg OUT;' + FROM #NoTransaction NT + ORDER BY OrderId + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') + ); + IF(@CleanUpProcedureExecutionCmd IS NOT NULL) + BEGIN + EXEC sys.sp_executesql @CleanUpProcedureExecutionCmd, N'@Result NVARCHAR(MAX) OUTPUT, @Msg NVARCHAR(MAX) OUTPUT', @Result OUT, @Msg OUT; + END; + + IF(@CleanUp IS NOT NULL) + BEGIN + EXEC tSQLt.Private_CleanUpCmdHandler @CleanUp, @Result OUT, @Msg OUT; + END; + + DECLARE @CleanUpErrorMsg NVARCHAR(MAX); + EXEC tSQLt.Private_CleanUp @FullTestName = @TestName, @Result = @Result OUT, @ErrorMsg = @CleanUpErrorMsg OUT; + SET @Msg = @Msg + ISNULL(' ' + @CleanUpErrorMsg, ''); + + SELECT object_id ObjectId, SCHEMA_NAME(schema_id) SchemaName, name ObjectName, type_desc ObjectType INTO #AfterExecutionObjectSnapshot FROM sys.objects; + EXEC tSQLt.Private_AssertNoSideEffects + @BeforeExecutionObjectSnapshotTableName ='#BeforeExecutionObjectSnapshot', + @AfterExecutionObjectSnapshotTableName = '#AfterExecutionObjectSnapshot', + @TestResult = @Result OUT, + @TestMsg = @Msg OUT + END; + IF(@TransactionStartedFlag = 1) + BEGIN + COMMIT; + END; END; GO CREATE PROCEDURE tSQLt.Private_RunTest @TestName NVARCHAR(MAX), - @SetUp NVARCHAR(MAX) = NULL + @SetUp NVARCHAR(MAX) = NULL, + @CleanUp NVARCHAR(MAX) = NULL AS BEGIN + DECLARE @OuterPerimeterTrancount INT = @@TRANCOUNT; + DECLARE @Msg NVARCHAR(MAX); SET @Msg = ''; DECLARE @Msg2 NVARCHAR(MAX); SET @Msg2 = ''; - DECLARE @Cmd NVARCHAR(MAX); SET @Cmd = ''; DECLARE @TestClassName NVARCHAR(MAX); SET @TestClassName = ''; DECLARE @TestProcName NVARCHAR(MAX); SET @TestProcName = ''; - DECLARE @Result NVARCHAR(MAX); SET @Result = 'Success'; - DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; + DECLARE @Result NVARCHAR(MAX); + DECLARE @TranName CHAR(32) = NULL; DECLARE @TestResultId INT; - DECLARE @PreExecTrancount INT; + DECLARE @TestObjectId INT; + DECLARE @TestEndTime DATETIME2 = NULL; DECLARE @VerboseMsg NVARCHAR(MAX); DECLARE @Verbose BIT; SET @Verbose = ISNULL((SELECT CAST(Value AS BIT) FROM tSQLt.Private_GetConfiguration('Verbose')),0); TRUNCATE TABLE tSQLt.CaptureOutputLog; + CREATE TABLE #TestMessage(Msg NVARCHAR(MAX)); CREATE TABLE #ExpectException(ExpectException INT,ExpectedMessage NVARCHAR(MAX), ExpectedSeverity INT, ExpectedState INT, ExpectedMessagePattern NVARCHAR(MAX), ExpectedErrorNumber INT, FailMessage NVARCHAR(MAX)); + CREATE TABLE #SkipTest(SkipTestMessage NVARCHAR(MAX) DEFAULT ''); + CREATE TABLE #NoTransaction(OrderId INT IDENTITY(1,1),CleanUpProcedureName NVARCHAR(MAX)); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + IF EXISTS (SELECT 1 FROM sys.extended_properties WHERE name = N'SetFakeViewOnTrigger') BEGIN @@ -63,10 +284,10 @@ BEGIN RETURN -1; END; - SELECT @Cmd = 'EXEC ' + @TestName; - SELECT @TestClassName = OBJECT_SCHEMA_NAME(OBJECT_ID(@TestName)), --tSQLt.Private_GetCleanSchemaName('', @TestName), - @TestProcName = tSQLt.Private_GetCleanObjectName(@TestName); + SELECT @TestClassName = OBJECT_SCHEMA_NAME(OBJECT_ID(@TestName)), + @TestProcName = tSQLt.Private_GetCleanObjectName(@TestName), + @TestObjectId = OBJECT_ID(@TestName); INSERT INTO tSQLt.TestResult(Class, TestCase, TranName, Result) SELECT @TestClassName, @TestProcName, @TranName, 'A severe error happened during test execution. Test did not finish.' @@ -79,143 +300,57 @@ BEGIN EXEC tSQLt.Private_Print @Message =@VerboseMsg, @Severity = 0; END; - BEGIN TRAN; - SAVE TRAN @TranName; - SET @PreExecTrancount = @@TRANCOUNT; - - TRUNCATE TABLE tSQLt.TestMessage; + SET @Result = 'Success'; + DECLARE @SkipTestFlag BIT = 0; + DECLARE @NoTransactionFlag BIT = 0; - DECLARE @TmpMsg NVARCHAR(MAX); - DECLARE @TestEndTime DATETIME; SET @TestEndTime = NULL; BEGIN TRY - IF (@SetUp IS NOT NULL) EXEC @SetUp; - EXEC (@Cmd); - SET @TestEndTime = GETDATE(); - IF(EXISTS(SELECT 1 FROM #ExpectException WHERE ExpectException = 1)) - BEGIN - SET @TmpMsg = COALESCE((SELECT FailMessage FROM #ExpectException)+' ','')+'Expected an error to be raised.'; - EXEC tSQLt.Fail @TmpMsg; - END - END TRY - BEGIN CATCH - SET @TestEndTime = ISNULL(@TestEndTime,GETDATE()); - IF ERROR_MESSAGE() LIKE '%tSQLt.Failure%' - BEGIN - SELECT @Msg = Msg FROM tSQLt.TestMessage; - SET @Result = 'Failure'; - END - ELSE - BEGIN - DECLARE @ErrorInfo NVARCHAR(MAX); - SELECT @ErrorInfo = - COALESCE(ERROR_MESSAGE(), '') + - '[' +COALESCE(LTRIM(STR(ERROR_SEVERITY())), '') + ','+COALESCE(LTRIM(STR(ERROR_STATE())), '') + ']' + - '{' + COALESCE(ERROR_PROCEDURE(), '') + ',' + COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '') + '}'; + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId=@TestObjectId; + SET @SkipTestFlag = CASE WHEN EXISTS(SELECT 1 FROM #SkipTest) THEN 1 ELSE 0 END; + SET @NoTransactionFlag = CASE WHEN EXISTS(SELECT 1 FROM #NoTransaction) THEN 1 ELSE 0 END; - IF(EXISTS(SELECT 1 FROM #ExpectException)) - BEGIN - DECLARE @ExpectException INT; - DECLARE @ExpectedMessage NVARCHAR(MAX); - DECLARE @ExpectedMessagePattern NVARCHAR(MAX); - DECLARE @ExpectedSeverity INT; - DECLARE @ExpectedState INT; - DECLARE @ExpectedErrorNumber INT; - DECLARE @FailMessage NVARCHAR(MAX); - SELECT @ExpectException = ExpectException, - @ExpectedMessage = ExpectedMessage, - @ExpectedSeverity = ExpectedSeverity, - @ExpectedState = ExpectedState, - @ExpectedMessagePattern = ExpectedMessagePattern, - @ExpectedErrorNumber = ExpectedErrorNumber, - @FailMessage = FailMessage - FROM #ExpectException; - - IF(@ExpectException = 1) - BEGIN - SET @Result = 'Success'; - SET @TmpMsg = COALESCE(@FailMessage+' ','')+'Exception did not match expectation!'; - IF(ERROR_MESSAGE() <> @ExpectedMessage) - BEGIN - SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ - 'Expected Message: <'+@ExpectedMessage+'>'+CHAR(13)+CHAR(10)+ - 'Actual Message : <'+ERROR_MESSAGE()+'>'; - SET @Result = 'Failure'; - END - IF(ERROR_MESSAGE() NOT LIKE @ExpectedMessagePattern) - BEGIN - SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ - 'Expected Message to be like <'+@ExpectedMessagePattern+'>'+CHAR(13)+CHAR(10)+ - 'Actual Message : <'+ERROR_MESSAGE()+'>'; - SET @Result = 'Failure'; - END - IF(ERROR_NUMBER() <> @ExpectedErrorNumber) - BEGIN - SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ - 'Expected Error Number: '+CAST(@ExpectedErrorNumber AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+ - 'Actual Error Number : '+CAST(ERROR_NUMBER() AS NVARCHAR(MAX)); - SET @Result = 'Failure'; - END - IF(ERROR_SEVERITY() <> @ExpectedSeverity) - BEGIN - SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ - 'Expected Severity: '+CAST(@ExpectedSeverity AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+ - 'Actual Severity : '+CAST(ERROR_SEVERITY() AS NVARCHAR(MAX)); - SET @Result = 'Failure'; - END - IF(ERROR_STATE() <> @ExpectedState) - BEGIN - SET @TmpMsg = @TmpMsg +CHAR(13)+CHAR(10)+ - 'Expected State: '+CAST(@ExpectedState AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+ - 'Actual State : '+CAST(ERROR_STATE() AS NVARCHAR(MAX)); - SET @Result = 'Failure'; - END - IF(@Result = 'Failure') - BEGIN - SET @Msg = @TmpMsg; - END - END - ELSE - BEGIN - SET @Result = 'Failure'; - SET @Msg = - COALESCE(@FailMessage+' ','')+ - 'Expected no error to be raised. Instead this error was encountered:'+ - CHAR(13)+CHAR(10)+ - @ErrorInfo; - END - END - ELSE - BEGIN - SET @Result = 'Error'; - SET @Msg = @ErrorInfo; - END + IF(@SkipTestFlag = 0) + BEGIN + IF(@NoTransactionFlag = 0) + BEGIN + EXEC tSQLt.GetNewTranName @TranName OUT; + UPDATE tSQLt.TestResult SET TranName = @TranName WHERE Id = @TestResultId; END; - END CATCH - - BEGIN TRY - ROLLBACK TRAN @TranName; + EXEC tSQLt.Private_RunTest_TestExecution + @TestName, + @SetUp, + @CleanUp, + @NoTransactionFlag, + @TranName, + @Result OUT, + @Msg OUT, + @TestEndTime OUT; + + END; + ELSE + BEGIN + DECLARE @TmpMsg NVARCHAR(MAX); + SELECT + @Result = 'Skipped', + @Msg = ST.SkipTestMessage + FROM #SkipTest AS ST; + SET @TmpMsg = '-->'+@TestName+' skipped: '+@Msg; + EXEC tSQLt.Private_Print @Message = @TmpMsg; + SET @TestEndTime = SYSDATETIME(); + END; END TRY BEGIN CATCH - DECLARE @PostExecTrancount INT; - SET @PostExecTrancount = @PreExecTrancount - @@TRANCOUNT; - IF (@@TRANCOUNT > 0) ROLLBACK; - BEGIN TRAN; - IF( @Result <> 'Success' - OR @PostExecTrancount <> 0 - ) - BEGIN - SELECT @Msg = COALESCE(@Msg, '') + ' (There was also a ROLLBACK ERROR --> ' + COALESCE(ERROR_MESSAGE(), '') + '{' + COALESCE(ERROR_PROCEDURE(), '') + ',' + COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '') + '})'; - SET @Result = 'Error'; - END - END CATCH - - If(@Result <> 'Success') + SET @Result = 'Error'; + SET @Msg = ISNULL(NULLIF(@Msg,'') + ' ','')+ERROR_MESSAGE(); + --SET @TestEndTime = SYSDATETIME(); + END CATCH; +---------------------------------------------------------------------------------------------- + If(@Result NOT IN ('Success','Skipped')) BEGIN SET @Msg2 = @TestName + ' failed: (' + @Result + ') ' + @Msg; EXEC tSQLt.Private_Print @Message = @Msg2, @Severity = 0; - END - + END; IF EXISTS(SELECT 1 FROM tSQLt.TestResult WHERE Id = @TestResultId) BEGIN UPDATE tSQLt.TestResult SET @@ -223,7 +358,7 @@ BEGIN Msg = @Msg, TestEndTime = @TestEndTime WHERE Id = @TestResultId; - END + END; ELSE BEGIN INSERT tSQLt.TestResult(Class, TestCase, TranName, Result, Msg) @@ -232,17 +367,28 @@ BEGIN '?', 'Error', 'TestResult entry is missing; Original outcome: ' + @Result + ', ' + @Msg; - END - - - COMMIT; + END; IF(@Verbose = 1) BEGIN - SET @VerboseMsg = 'tSQLt.Run '''+@TestName+'''; --Finished'; + SET @VerboseMsg = 'tSQLt.Run '''+@TestName+'''; --Finished'; EXEC tSQLt.Private_Print @Message =@VerboseMsg, @Severity = 0; + --DECLARE @AsciiArtLine NVARCHAR(MAX) = CASE WHEN @Result<>'Success' THEN REPLICATE(CHAR(168),150)+' '+CHAR(155)+CHAR(155)+' '+@Result + ' ' +CHAR(139)+CHAR(139) ELSE '' END + CHAR(13)+CHAR(10) + CHAR(173); + --EXEC tSQLt.Private_Print @Message = @AsciiArtLine, @Severity = 0; END; + IF(@Result = 'FATAL') + BEGIN + INSERT INTO tSQLt.Private_Seize VALUES(1); + RAISERROR('The last test has invalidated the current installation of tSQLt. Please reinstall tSQLt.',16,10); + END; + IF(@Result = 'Abort') + BEGIN + RAISERROR('Aborting the current execution of tSQLt due to a severe error.', 16, 10); + END; + + IF(@OuterPerimeterTrancount != @@TRANCOUNT) RAISERROR('tSQLt is in an invalid state: Stopping Execution. (Mismatching TRANCOUNT: %i <> %i))',16,10,@OuterPerimeterTrancount, @@TRANCOUNT); + END; GO @@ -253,28 +399,20 @@ BEGIN DECLARE @TestCaseName NVARCHAR(MAX); DECLARE @TestClassId INT; SET @TestClassId = tSQLt.Private_GetSchemaId(@TestClassName); DECLARE @SetupProcName NVARCHAR(MAX); - EXEC tSQLt.Private_GetSetupProcedureName @TestClassId, @SetupProcName OUTPUT; - - DECLARE testCases CURSOR LOCAL FAST_FORWARD - FOR - SELECT tSQLt.Private_GetQuotedFullName(object_id) - FROM sys.procedures - WHERE schema_id = @TestClassId - AND LOWER(name) LIKE 'test%'; - - OPEN testCases; + DECLARE @CleanUpProcName NVARCHAR(MAX); + EXEC tSQLt.Private_GetClassHelperProcedureName @TestClassId, @SetupProcName OUT, @CleanUpProcName OUT; - FETCH NEXT FROM testCases INTO @TestCaseName; - - WHILE @@FETCH_STATUS = 0 - BEGIN - EXEC tSQLt.Private_RunTest @TestCaseName, @SetupProcName; - - FETCH NEXT FROM testCases INTO @TestCaseName; - END; - - CLOSE testCases; - DEALLOCATE testCases; + DECLARE @cmd NVARCHAR(MAX) = ( + ( + SELECT 'EXEC tSQLt.Private_RunTest '''+REPLACE(tSQLt.Private_GetQuotedFullName(object_id),'''','''''')+''', '+ISNULL(''''+REPLACE(@SetupProcName,'''','''''')+'''','NULL')+', '+ISNULL(''''+REPLACE(@CleanUpProcName,'''','''''')+'''','NULL')+';' + FROM sys.procedures + WHERE schema_id = @TestClassId + AND LOWER(name) LIKE 'test%' + ORDER BY NEWID() + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') + ); + EXEC(@cmd); END; GO @@ -291,7 +429,7 @@ SET NOCOUNT ON; DECLARE @IsSchema BIT; DECLARE @SetUp NVARCHAR(MAX);SET @SetUp = NULL; - SELECT @TestName = tSQLt.Private_GetLastTestNameIfNotProvided(@TestName); + SELECT @TestName = TestName FROM tSQLt.Private_GetLastTestNameIfNotProvided(@TestName); EXEC tSQLt.Private_SaveTestNameForSession @TestName; SELECT @TestClassId = schemaId, @@ -309,9 +447,10 @@ SET NOCOUNT ON; IF @IsTestCase = 1 BEGIN DECLARE @SetupProcName NVARCHAR(MAX); - EXEC tSQLt.Private_GetSetupProcedureName @TestClassId, @SetupProcName OUTPUT; + DECLARE @CleanUpProcName NVARCHAR(MAX); + EXEC tSQLt.Private_GetClassHelperProcedureName @TestClassId, @SetupProcName OUT, @CleanUpProcName OUT; - EXEC tSQLt.Private_RunTest @FullName, @SetupProcName; + EXEC tSQLt.Private_RunTest @FullName, @SetupProcName, @CleanUpProcName; END; EXEC tSQLt.Private_OutputTestResults @TestResultFormatter; @@ -328,34 +467,28 @@ BEGIN DECLARE @TestClassName NVARCHAR(MAX); DECLARE @TestProcName NVARCHAR(MAX); - DECLARE @TestClassCursor CURSOR; - EXEC @GetCursorCallback @TestClassCursor = @TestClassCursor OUT; + CREATE TABLE #TestClassesForRunCursor(Name NVARCHAR(MAX)); + EXEC @GetCursorCallback; ---- - WHILE(1=1) - BEGIN - FETCH NEXT FROM @TestClassCursor INTO @TestClassName; - IF(@@FETCH_STATUS<>0)BREAK; - - EXEC tSQLt.Private_RunTestClass @TestClassName; - - END; - - CLOSE @TestClassCursor; - DEALLOCATE @TestClassCursor; + DECLARE @cmd NVARCHAR(MAX) = ( + ( + SELECT 'EXEC tSQLt.Private_RunTestClass '''+REPLACE(Name, '''' ,'''''')+''';' + FROM #TestClassesForRunCursor + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') + ); + EXEC(@cmd); EXEC tSQLt.Private_OutputTestResults @TestResultFormatter; END; GO CREATE PROCEDURE tSQLt.Private_GetCursorForRunAll - @TestClassCursor CURSOR VARYING OUTPUT AS BEGIN - SET @TestClassCursor = CURSOR LOCAL FAST_FORWARD FOR + INSERT INTO #TestClassesForRunCursor SELECT Name FROM tSQLt.TestClasses; - - OPEN @TestClassCursor; END; GO @@ -368,16 +501,13 @@ END; GO CREATE PROCEDURE tSQLt.Private_GetCursorForRunNew - @TestClassCursor CURSOR VARYING OUTPUT AS BEGIN - SET @TestClassCursor = CURSOR LOCAL FAST_FORWARD FOR + INSERT INTO #TestClassesForRunCursor SELECT TC.Name FROM tSQLt.TestClasses AS TC JOIN tSQLt.Private_NewTestClassList AS PNTCL ON PNTCL.ClassName = TC.Name; - - OPEN @TestClassCursor; END; GO @@ -449,7 +579,7 @@ CREATE PROCEDURE tSQLt.Private_InputBuffer @InputBuffer NVARCHAR(MAX) OUTPUT AS BEGIN - CREATE TABLE #inputbuffer(EventType SYSNAME, Parameters SMALLINT, EventInfo NVARCHAR(MAX)); + CREATE TABLE #inputbuffer(EventType sysname, Parameters SMALLINT, EventInfo NVARCHAR(MAX)); INSERT INTO #inputbuffer EXEC('DBCC INPUTBUFFER(@@SPID) WITH NO_INFOMSGS;'); SELECT @InputBuffer = I.EventInfo FROM #inputbuffer AS I; @@ -484,45 +614,60 @@ BEGIN EXEC tSQLt.Run @TestName = @TestName, @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; END; GO - +CREATE FUNCTION tSQLt.Private_PrepareTestResultForOutput() +RETURNS TABLE +AS +RETURN + SELECT ROW_NUMBER() OVER(ORDER BY Result DESC, Name ASC) No,Name [Test Case Name], + RIGHT(SPACE(7)+CAST(DATEDIFF(MILLISECOND,TestStartTime,TestEndTime) AS VARCHAR(7)),7) AS [Dur(ms)], Result + FROM tSQLt.TestResult; +GO CREATE PROCEDURE tSQLt.DefaultResultFormatter AS BEGIN - DECLARE @Msg1 NVARCHAR(MAX); - DECLARE @Msg2 NVARCHAR(MAX); - DECLARE @Msg3 NVARCHAR(MAX); - DECLARE @Msg4 NVARCHAR(MAX); + DECLARE @TestList NVARCHAR(MAX); + DECLARE @Dashes NVARCHAR(MAX); + DECLARE @CountSummaryMsg NVARCHAR(MAX); + DECLARE @NewLine NVARCHAR(MAX); DECLARE @IsSuccess INT; DECLARE @SuccessCnt INT; DECLARE @Severity INT; + DECLARE @SummaryError INT; - SELECT ROW_NUMBER() OVER(ORDER BY Result DESC, Name ASC) No,Name [Test Case Name], - RIGHT(SPACE(7)+CAST(DATEDIFF(MILLISECOND,TestStartTime,TestEndTime) AS VARCHAR(7)),7) AS [Dur(ms)], Result + SELECT * INTO #TestResultOutput - FROM tSQLt.TestResult; + FROM tSQLt.Private_PrepareTestResultForOutput() AS PTRFO; - EXEC tSQLt.TableToText @Msg1 OUTPUT, '#TestResultOutput', 'No'; + EXEC tSQLt.TableToText @TestList OUTPUT, '#TestResultOutput', 'No'; - SELECT @Msg3 = Msg, + SELECT @CountSummaryMsg = Msg, @IsSuccess = 1 - SIGN(FailCnt + ErrorCnt), @SuccessCnt = SuccessCnt FROM tSQLt.TestCaseSummary(); + SELECT @SummaryError = CAST(PC.Value AS INT) + FROM tSQLt.Private_Configurations AS PC + WHERE PC.Name = 'SummaryError'; + SELECT @Severity = 16*(1-@IsSuccess); + IF(@SummaryError = 0) + BEGIN + SET @Severity = 0; + END; - SELECT @Msg2 = REPLICATE('-',LEN(@Msg3)), - @Msg4 = CHAR(13)+CHAR(10); + SELECT @Dashes = REPLICATE('-',LEN(@CountSummaryMsg)), + @NewLine = CHAR(13)+CHAR(10); - EXEC tSQLt.Private_Print @Msg4,0; + EXEC tSQLt.Private_Print @NewLine,0; EXEC tSQLt.Private_Print '+----------------------+',0; EXEC tSQLt.Private_Print '|Test Execution Summary|',0; EXEC tSQLt.Private_Print '+----------------------+',0; - EXEC tSQLt.Private_Print @Msg4,0; - EXEC tSQLt.Private_Print @Msg1,0; - EXEC tSQLt.Private_Print @Msg2,0; - EXEC tSQLt.Private_Print @Msg3, @Severity; - EXEC tSQLt.Private_Print @Msg2,0; + EXEC tSQLt.Private_Print @NewLine,0; + EXEC tSQLt.Private_Print @TestList,0; + EXEC tSQLt.Private_Print @Dashes,0; + EXEC tSQLt.Private_Print @CountSummaryMsg, @Severity; + EXEC tSQLt.Private_Print @Dashes,0; END; GO @@ -542,6 +687,7 @@ BEGIN NULL AS [testsuite!2!tests], NULL AS [testsuite!2!errors], NULL AS [testsuite!2!failures], + NULL AS [testsuite!2!skipped], NULL AS [testsuite!2!timestamp], NULL AS [testsuite!2!time], NULL AS [testsuite!2!hostname], @@ -554,8 +700,10 @@ BEGIN NULL AS [failure!5!type], NULL AS [error!6!message], NULL AS [error!6!type], - NULL AS [system-out!7!hide], - NULL AS [system-err!8!hide] + NULL AS [skipped!7!message], + NULL AS [skipped!7!type], + NULL AS [system-out!8!hide], + NULL AS [system-err!9!hide] UNION ALL SELECT 2 AS Tag, 1 AS Parent, @@ -565,6 +713,7 @@ BEGIN COUNT(1), SUM(CASE Result WHEN 'Error' THEN 1 ELSE 0 END), SUM(CASE Result WHEN 'Failure' THEN 1 ELSE 0 END), + SUM(CASE Result WHEN 'Skipped' THEN 1 ELSE 0 END), CONVERT(VARCHAR(19),MIN(TestResult.TestStartTime),126), CAST(CAST(DATEDIFF(MILLISECOND,MIN(TestResult.TestStartTime),MAX(TestResult.TestEndTime))/1000.0 AS NUMERIC(20,3))AS VARCHAR(MAX)), CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(MAX)), @@ -578,6 +727,8 @@ BEGIN NULL, NULL, NULL, + NULL, + NULL, NULL FROM tSQLt.TestResult GROUP BY Class @@ -595,6 +746,7 @@ BEGIN NULL, NULL, NULL, + NULL, Class, NULL, NULL, @@ -603,6 +755,8 @@ BEGIN NULL, NULL, NULL, + NULL, + NULL, NULL FROM tSQLt.TestResult GROUP BY Class @@ -620,6 +774,7 @@ BEGIN NULL, NULL, NULL, + NULL, Class, TestCase, CAST(CAST(DATEDIFF(MILLISECOND,TestResult.TestStartTime,TestResult.TestEndTime)/1000.0 AS NUMERIC(20,3))AS VARCHAR(MAX)), @@ -628,6 +783,8 @@ BEGIN NULL, NULL, NULL, + NULL, + NULL, NULL FROM tSQLt.TestResult UNION ALL @@ -644,6 +801,7 @@ BEGIN NULL, NULL, NULL, + NULL, Class, TestCase, CAST(CAST(DATEDIFF(MILLISECOND,TestResult.TestStartTime,TestResult.TestEndTime)/1000.0 AS NUMERIC(20,3))AS VARCHAR(MAX)), @@ -652,6 +810,8 @@ BEGIN NULL, NULL, NULL, + NULL, + NULL, NULL FROM tSQLt.TestResult WHERE Result IN ('Failure') @@ -669,6 +829,7 @@ BEGIN NULL, NULL, NULL, + NULL, Class, TestCase, CAST(CAST(DATEDIFF(MILLISECOND,TestResult.TestStartTime,TestResult.TestEndTime)/1000.0 AS NUMERIC(20,3))AS VARCHAR(MAX)), @@ -677,11 +838,41 @@ BEGIN Msg, 'SQL Error', NULL, + NULL, + NULL, NULL FROM tSQLt.TestResult WHERE Result IN ( 'Error') UNION ALL SELECT 7 AS Tag, + 4 AS Parent, + 'root', + NULL, + Class, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + Class, + TestCase, + CAST(CAST(DATEDIFF(MILLISECOND,TestResult.TestStartTime,TestResult.TestEndTime)/1000.0 AS NUMERIC(20,3))AS VARCHAR(MAX)), + NULL, + NULL, + NULL, + NULL, + Msg, + NULL, + NULL, + NULL + FROM tSQLt.TestResult + WHERE Result IN ( 'Skipped') + UNION ALL + SELECT 8 AS Tag, 2 AS Parent, 'root', NULL, @@ -694,6 +885,7 @@ BEGIN NULL, NULL, NULL, + NULL, Class, NULL, NULL, @@ -702,11 +894,13 @@ BEGIN NULL, NULL, NULL, + NULL, + NULL, NULL FROM tSQLt.TestResult GROUP BY Class UNION ALL - SELECT 8 AS Tag, + SELECT 9 AS Tag, 2 AS Parent, 'root', NULL, @@ -719,6 +913,7 @@ BEGIN NULL, NULL, NULL, + NULL, Class, NULL, NULL, @@ -727,11 +922,13 @@ BEGIN NULL, NULL, NULL, + NULL, + NULL, NULL FROM tSQLt.TestResult GROUP BY Class ) AS X - ORDER BY [testsuite!2!name],CASE WHEN Tag IN (7,8) THEN 1 ELSE 0 END, [testcase!4!name], Tag + ORDER BY [testsuite!2!name],CASE WHEN Tag IN (8,9) THEN 1 ELSE 0 END, [testcase!4!name], Tag FOR XML EXPLICIT ); @@ -754,3 +951,14 @@ BEGIN END GO --Build- + + + + --SELECT 3 X, @SkipTestFlag SkipTestFlag, + -- @NoTransactionFlag NoTransactionFlag, + -- @TransactionStartedFlag TransactionStartedFlag, + -- @PreExecTrancount PreExecTrancount, + -- @@TRANCOUNT Trancount, + -- @TestName TestName, + -- @Result Result, + -- @Msg Msg; diff --git a/Source/Source.sqlproj b/Source/Source.sqlproj new file mode 100644 index 000000000..93e598a47 --- /dev/null +++ b/Source/Source.sqlproj @@ -0,0 +1,13 @@ + + + + + Source + {57B28F7E-3312-4E55-A6FA-75019BDACA5B} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + 1033, CI + + + + + \ No newline at end of file diff --git a/Source/Source.ssmssqlproj b/Source/Source.ssmssqlproj deleted file mode 100644 index ed043c2a4..000000000 --- a/Source/Source.ssmssqlproj +++ /dev/null @@ -1,545 +0,0 @@ - - - - - - - 2015-07-26T23:07:57.2567069-04:00 - SQL - Dev_2005 - - Windows Authentication - tempdb - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - 2014-07-30T13:44:05.3611369-04:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ApplyConstraint_Methods.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - InWork.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Run_Methods.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - SetClrEnabled.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt._Footer.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.ApplyTrigger.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.AssertEmptyTable.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.AssertEqualsString.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.AssertEqualsTable.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.AssertEqualsTableSchema.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.AssertEqualsTableSchema.tables.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - sqlity.net - tSQLt.AssertLike.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.AssertNotEquals.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - sqlity.net - tSQLt.AssertObjectDoesNotExist.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.AssertObjectExists.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.AssertStringIn.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.AssertStringTable.udt.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.CaptureOutputLog.tbl.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.DropClass.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.EnableExternalAccess.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.ExpectException.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.ExpectNoException.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - sqlity.net - tSQLt.Fail.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.FakeFunction.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Info.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.InstallExternalAccessKey.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.LogCapturedOutput.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.NewTestClass.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_Bin2Hex.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_CleanTestResult.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_CompareTables.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_CompareTablesFailIfUnequalRowsExists.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_Configurations.tbl.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_CreateFakeFunction.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_CreateProcedureSpy.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_CreateResultTableForCompareTables.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_DisallowOverwritingNonTestSchema.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_GetCommaSeparatedColumnList.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_GetConfiguration.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_GetDataTypeOrComputedColumnDefinition.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_GetDefaultConstraintDefinition.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_GetExternalAccessKeyBytes.mdl.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_GetForeignKeyDefinition.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_GetUniqueConstraintDefinition.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_Init.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_MarkSchemaAsTestClass.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_NewTestClassList.tbl.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_NullCellTable.tbl.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_QuoteClassNameForNewTestClass.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_RemoveSchemaBinding.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_RemoveSchemaBoundReferences.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_RenamedObjectLog.tbl.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_MarkObjectBeforeRename.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_RenameObjectToUniqueName.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.FakeTable.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_GetIdentityDefinition.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_CreateFakeOfTable.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_GetFullTypeName.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_MarkFakeTable.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_RenameObjectToUniqueNameUsingObjectId.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_ResetNewTestClassList.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_ScriptIndex.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_SetConfiguration.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_SqlVariantFormatter.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_SqlVersion.sfn.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_SysIndexes.svw.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_SysTypes.svw.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_ValidateFakeTableParameters.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Private_ValidateThatAllDataTypesInTableAreSupported.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.RemoveExternalAccessKey.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.RemoveObject.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.RemoveObjectIfExists.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.RenameClass.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.Reset.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.SetVerbose.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.SpyProcedure.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.StubRecord.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.TableToText.ssp.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt._Header.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLtCLR.mdl.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLtCLR_CreateProcs.sql - - - - - - - - - - BuildOrder.txt - - - - - \ No newline at end of file diff --git a/Source/tSQLt.(at)tSQLt_MaxSqlMajorVersion.sfn.sql b/Source/tSQLt.(at)tSQLt_MaxSqlMajorVersion.sfn.sql new file mode 100644 index 000000000..32ecd2208 --- /dev/null +++ b/Source/tSQLt.(at)tSQLt_MaxSqlMajorVersion.sfn.sql @@ -0,0 +1,20 @@ +IF OBJECT_ID('tSQLt.[@tSQLt:MaxSqlMajorVersion]') IS NOT NULL DROP FUNCTION tSQLt.[@tSQLt:MaxSqlMajorVersion]; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.[@tSQLt:MaxSqlMajorVersion](@MaxVersion INT) +RETURNS TABLE +AS +RETURN + SELECT AF.* + FROM (SELECT PSSV.Major FROM tSQLt.Private_SqlVersion() AS PSV CROSS APPLY tSQLt.Private_SplitSqlVersion(PSV.ProductVersion) AS PSSV) AV + CROSS APPLY tSQLt.[@tSQLt:SkipTest]('Maximum allowed version is '+ + CAST(@MaxVersion AS NVARCHAR(MAX))+ + ', but current version is '+ + CAST(AV.Major AS NVARCHAR(MAX))+'.' + ) AS AF + WHERE @MaxVersion < AV.Major +GO +---Build- +GO + diff --git a/Source/tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql b/Source/tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql new file mode 100644 index 000000000..0f970159b --- /dev/null +++ b/Source/tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql @@ -0,0 +1,25 @@ +IF OBJECT_ID('tSQLt.[@tSQLt:MinSqlMajorVersion]') IS NOT NULL DROP FUNCTION tSQLt.[@tSQLt:MinSqlMajorVersion]; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.[@tSQLt:MinSqlMajorVersion](@MinVersion INT) +RETURNS TABLE +AS +RETURN + SELECT AF.* + FROM + ( + SELECT PSSV.Major + FROM tSQLt.Private_SqlVersion() AS PSV + CROSS APPLY tSQLt.Private_SplitSqlVersion(PSV.ProductVersion) AS PSSV + ) AV + CROSS APPLY tSQLt.[@tSQLt:SkipTest]('Minimum required version is '+ + CAST(@MinVersion AS NVARCHAR(MAX))+ + ', but current version is '+ + CAST(AV.Major AS NVARCHAR(MAX))+'.' + ) AS AF + WHERE @MinVersion > AV.Major +GO +---Build- +GO + diff --git a/Source/tSQLt.(at)tSQLt_NoTransaction.sfn.sql b/Source/tSQLt.(at)tSQLt_NoTransaction.sfn.sql new file mode 100644 index 000000000..60bb2c6af --- /dev/null +++ b/Source/tSQLt.(at)tSQLt_NoTransaction.sfn.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.[@tSQLt:NoTransaction]') IS NOT NULL DROP FUNCTION tSQLt.[@tSQLt:NoTransaction]; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.[@tSQLt:NoTransaction](@CleanUpProcedureName NVARCHAR(MAX) = NULL) +RETURNS TABLE +AS +RETURN + SELECT + CASE + WHEN (X.QuotedName IS NULL) + THEN 'INSERT INTO #NoTransaction VALUES(NULL);' + ELSE 'IF(NOT EXISTS (SELECT 1 FROM sys.procedures WHERE object_id = OBJECT_ID('+X.QuotedName+'))) BEGIN RAISERROR(''Test CleanUp Procedure %s does not exist or is not a procedure.'',16,10,'+X.QuotedName+'); END;INSERT INTO #NoTransaction VALUES('+X.QuotedName+');' + END AS AnnotationCmd + FROM (VALUES(''''+REPLACE(@CleanUpProcedureName,'''','''''')+''''))X(QuotedName); +GO +---Build- +GO diff --git a/Source/tSQLt.(at)tSQLt_RunOnlyOnHostPlatform.sfn.sql b/Source/tSQLt.(at)tSQLt_RunOnlyOnHostPlatform.sfn.sql new file mode 100644 index 000000000..31493f225 --- /dev/null +++ b/Source/tSQLt.(at)tSQLt_RunOnlyOnHostPlatform.sfn.sql @@ -0,0 +1,20 @@ +IF OBJECT_ID('tSQLt.[@tSQLt:RunOnlyOnHostPlatform]') IS NOT NULL DROP FUNCTION tSQLt.[@tSQLt:RunOnlyOnHostPlatform]; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.[@tSQLt:RunOnlyOnHostPlatform](@HostPlatform NVARCHAR(MAX)) +RETURNS TABLE +AS +RETURN + SELECT SkipTestFunction.* + FROM (SELECT I.HostPlatform FROM tSQLt.Info() AS I WHERE I.HostPlatform <> @HostPlatform) AV + CROSS APPLY tSQLt.[@tSQLt:SkipTest]('HostPlatform is required to be '''+ + @HostPlatform + + ''', but is '''+ + AV.HostPlatform + + '''.' + ) AS SkipTestFunction; +GO +---Build- +GO + diff --git a/Source/tSQLt.(at)tSQLt_SkipTest.sfn.sql b/Source/tSQLt.(at)tSQLt_SkipTest.sfn.sql new file mode 100644 index 000000000..8fdd87aa0 --- /dev/null +++ b/Source/tSQLt.(at)tSQLt_SkipTest.sfn.sql @@ -0,0 +1,14 @@ +IF OBJECT_ID('tSQLt.[@tSQLt:SkipTest]') IS NOT NULL DROP FUNCTION tSQLt.[@tSQLt:SkipTest]; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.[@tSQLt:SkipTest](@SkipReason NVARCHAR(MAX)) +RETURNS TABLE +AS +RETURN + SELECT 'EXEC tSQLt.Private_SkipTestAnnotationHelper @SkipReason = '''+ + ISNULL(NULLIF(REPLACE(@SkipReason,'''',''''''),''),'')+ + ''';' AS AnnotationCmd; +GO +---Build- +GO diff --git a/Source/tSQLt.ApplyTrigger.ssp.sql b/Source/tSQLt.ApplyTrigger.ssp.sql index 1de2ed121..b5ded0b04 100644 --- a/Source/tSQLt.ApplyTrigger.ssp.sql +++ b/Source/tSQLt.ApplyTrigger.ssp.sql @@ -7,6 +7,8 @@ CREATE PROCEDURE tSQLt.ApplyTrigger AS BEGIN DECLARE @OrgTableObjectId INT; + DECLARE @NewNameOfOriginalTrigger NVARCHAR(MAX); + SELECT @OrgTableObjectId = OrgTableObjectId FROM tSQLt.Private_GetOriginalTableInfo(OBJECT_ID(@TableName)) orgTbl IF(@OrgTableObjectId IS NULL) BEGIN @@ -28,9 +30,11 @@ BEGIN RAISERROR('%s is not a trigger on %s', 16, 10, @TriggerName, @TableName); END; - EXEC tSQLt.RemoveObject @FullTriggerName; + EXEC tSQLt.RemoveObject @ObjectName = @FullTriggerName, @NewName = @NewNameOfOriginalTrigger OUTPUT; EXEC(@TriggerCode); + + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = @FullTriggerName, @ObjectType = N'TRIGGER', @NewNameOfOriginalObject = @NewNameOfOriginalTrigger; END; ---Build- GO diff --git a/Source/tSQLt.AssertEqualsTable.ssp.sql b/Source/tSQLt.AssertEqualsTable.ssp.sql index 555f05222..64439fd10 100644 --- a/Source/tSQLt.AssertEqualsTable.ssp.sql +++ b/Source/tSQLt.AssertEqualsTable.ssp.sql @@ -13,6 +13,7 @@ BEGIN EXEC tSQLt.AssertObjectExists @Actual; DECLARE @ResultTable NVARCHAR(MAX); + DECLARE @ResultTableWithSchema NVARCHAR(MAX); DECLARE @ResultColumn NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); DECLARE @UnequalRowsExist INT; @@ -20,27 +21,28 @@ BEGIN SELECT @ResultTable = tSQLt.Private::CreateUniqueObjectName(); SELECT @ResultColumn = 'RC_' + @ResultTable; + SELECT @ResultTableWithSchema = 'tSQLt.' + @ResultTable; EXEC tSQLt.Private_CreateResultTableForCompareTables - @ResultTable = @ResultTable, + @ResultTable = @ResultTableWithSchema, @ResultColumn = @ResultColumn, @BaseTable = @Expected; - SELECT @ColumnList = tSQLt.Private_GetCommaSeparatedColumnList(@ResultTable, @ResultColumn); + SELECT @ColumnList = tSQLt.Private_GetCommaSeparatedColumnList(@ResultTableWithSchema, @ResultColumn); - EXEC tSQLt.Private_ValidateThatAllDataTypesInTableAreSupported @ResultTable, @ColumnList; + EXEC tSQLt.Private_ValidateThatAllDataTypesInTableAreSupported @ResultTableWithSchema, @ColumnList; EXEC @UnequalRowsExist = tSQLt.Private_CompareTables @Expected = @Expected, @Actual = @Actual, - @ResultTable = @ResultTable, + @ResultTable = @ResultTableWithSchema, @ColumnList = @ColumnList, @MatchIndicatorColumnName = @ResultColumn; SET @CombinedMessage = ISNULL(@Message + CHAR(13) + CHAR(10),'') + @FailMsg; EXEC tSQLt.Private_CompareTablesFailIfUnequalRowsExists @UnequalRowsExist = @UnequalRowsExist, - @ResultTable = @ResultTable, + @ResultTable = @ResultTableWithSchema, @ResultColumn = @ResultColumn, @ColumnList = @ColumnList, @FailMsg = @CombinedMessage; diff --git a/Source/tSQLt.AssertEqualsTableSchema.ssp.sql b/Source/tSQLt.AssertEqualsTableSchema.ssp.sql index bf1807dcb..b41c60c62 100644 --- a/Source/tSQLt.AssertEqualsTableSchema.ssp.sql +++ b/Source/tSQLt.AssertEqualsTableSchema.ssp.sql @@ -8,9 +8,8 @@ CREATE PROCEDURE tSQLt.AssertEqualsTableSchema @Message NVARCHAR(MAX) = NULL AS BEGIN - INSERT INTO tSQLt.Private_AssertEqualsTableSchema_Expected([RANK(column_id)],name,system_type_id,user_type_id,max_length,precision,scale,collation_name,is_nullable) SELECT - RANK()OVER(ORDER BY C.column_id), + RANK()OVER(ORDER BY C.column_id) [RANK(column_id)], C.name, CAST(C.system_type_id AS NVARCHAR(MAX))+QUOTENAME(TS.name) system_type_id, CAST(C.user_type_id AS NVARCHAR(MAX))+CASE WHEN TU.system_type_id<> TU.user_type_id THEN QUOTENAME(SCHEMA_NAME(TU.schema_id))+'.' ELSE '' END + QUOTENAME(TU.name) user_type_id, @@ -19,15 +18,15 @@ BEGIN C.scale, C.collation_name, C.is_nullable + INTO #Expected FROM sys.columns AS C JOIN sys.types AS TS ON C.system_type_id = TS.user_type_id JOIN sys.types AS TU ON C.user_type_id = TU.user_type_id WHERE C.object_id = OBJECT_ID(@Expected); - INSERT INTO tSQLt.Private_AssertEqualsTableSchema_Actual([RANK(column_id)],name,system_type_id,user_type_id,max_length,precision,scale,collation_name,is_nullable) SELECT - RANK()OVER(ORDER BY C.column_id), + RANK()OVER(ORDER BY C.column_id) [RANK(column_id)], C.name, CAST(C.system_type_id AS NVARCHAR(MAX))+QUOTENAME(TS.name) system_type_id, CAST(C.user_type_id AS NVARCHAR(MAX))+CASE WHEN TU.system_type_id<> TU.user_type_id THEN QUOTENAME(SCHEMA_NAME(TU.schema_id))+'.' ELSE '' END + QUOTENAME(TU.name) user_type_id, @@ -36,6 +35,7 @@ BEGIN C.scale, C.collation_name, C.is_nullable + INTO #Actual FROM sys.columns AS C JOIN sys.types AS TS ON C.system_type_id = TS.user_type_id @@ -43,6 +43,6 @@ BEGIN ON C.user_type_id = TU.user_type_id WHERE C.object_id = OBJECT_ID(@Actual); - EXEC tSQLt.AssertEqualsTable 'tSQLt.Private_AssertEqualsTableSchema_Expected','tSQLt.Private_AssertEqualsTableSchema_Actual',@Message=@Message,@FailMsg='Unexpected/missing column(s)'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual',@Message=@Message,@FailMsg='Unexpected/missing column(s)'; END; GO \ No newline at end of file diff --git a/Source/tSQLt.AssertEqualsTableSchema.tables.sql b/Source/tSQLt.AssertEqualsTableSchema.tables.sql deleted file mode 100644 index 58962af25..000000000 --- a/Source/tSQLt.AssertEqualsTableSchema.tables.sql +++ /dev/null @@ -1,28 +0,0 @@ -IF OBJECT_ID('tSQLt.Private_AssertEqualsTableSchema_Expected') IS NOT NULL DROP TABLE tSQLt.Private_AssertEqualsTableSchema_Expected; -IF OBJECT_ID('tSQLt.Private_AssertEqualsTableSchema_Actual') IS NOT NULL DROP TABLE tSQLt.Private_AssertEqualsTableSchema_Actual; -GO ----Build+ -GO -CREATE TABLE [tSQLt].[Private_AssertEqualsTableSchema_Actual] -( - name NVARCHAR(256) NULL, - [RANK(column_id)] INT NULL, - system_type_id NVARCHAR(MAX) NULL, - user_type_id NVARCHAR(MAX) NULL, - max_length SMALLINT NULL, - precision TINYINT NULL, - scale TINYINT NULL, - collation_name NVARCHAR(256) NULL, - is_nullable BIT NULL, - is_identity BIT NULL -); -GO -EXEC(' - SET NOCOUNT ON; - SELECT TOP(0) * - INTO tSQLt.Private_AssertEqualsTableSchema_Expected - FROM tSQLt.Private_AssertEqualsTableSchema_Actual AS AETSA; -'); -GO ----Build- -GO \ No newline at end of file diff --git a/Source/tSQLt.DropClass.ssp.sql b/Source/tSQLt.DropClass.ssp.sql index 55c94c2ea..a2b181229 100644 --- a/Source/tSQLt.DropClass.ssp.sql +++ b/Source/tSQLt.DropClass.ssp.sql @@ -5,63 +5,74 @@ CREATE PROCEDURE tSQLt.DropClass @ClassName NVARCHAR(MAX) AS BEGIN +/*SnipStart: CreateDropClassStatement.ps1*/ DECLARE @Cmd NVARCHAR(MAX); - WITH ObjectInfo(name, type) AS + WITH SchemaInfo(FullName, ItemType, SchemaId) AS ( - SELECT QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name) , O.type + SELECT + QUOTENAME(S.name), + 'schema', + S.schema_id + FROM sys.schemas AS S + WHERE S.schema_id = ISNULL(SCHEMA_ID(@ClassName), SCHEMA_ID(PARSENAME(@ClassName,1))) + ), + ConstraintInfo(FullName, ItemType) AS + (/*FOREIGN KEYS need to be dropped before their tables*/ + SELECT + QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name), + O.type FROM sys.objects AS O - WHERE O.schema_id = SCHEMA_ID(@ClassName) + JOIN SchemaInfo SI ON SI.SchemaId = O.schema_id + AND O.type IN ('F') ), - TypeInfo(name) AS + ObjectInfo(FullName, ItemType) AS ( - SELECT QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name) + SELECT + QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name), + O.type + FROM sys.objects AS O + JOIN SchemaInfo SI ON SI.SchemaId = O.schema_id + AND O.type NOT IN ('F') + ), + TypeInfo(FullName, ItemType) AS + ( + SELECT + QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name), + 'type' FROM sys.types AS T - WHERE T.schema_id = SCHEMA_ID(@ClassName) + JOIN SchemaInfo SI ON SI.SchemaId = T.schema_id ), - XMLSchemaInfo(name) AS + XMLSchemaInfo(FullName, ItemType) AS ( - SELECT QUOTENAME(SCHEMA_NAME(XSC.schema_id))+'.'+QUOTENAME(XSC.name) + SELECT + QUOTENAME(SCHEMA_NAME(XSC.schema_id))+'.'+QUOTENAME(XSC.name), + 'xml_schema_collection' FROM sys.xml_schema_collections AS XSC - WHERE XSC.schema_id = SCHEMA_ID(@ClassName) + JOIN SchemaInfo SI ON SI.SchemaId = XSC.schema_id ), - DropStatements(no,cmd) AS + DropStatements(no,FullName,ItemType) AS ( - SELECT 10, - 'DROP ' + - CASE type WHEN 'P' THEN 'PROCEDURE' - WHEN 'PC' THEN 'PROCEDURE' - WHEN 'U' THEN 'TABLE' - WHEN 'IF' THEN 'FUNCTION' - WHEN 'TF' THEN 'FUNCTION' - WHEN 'FN' THEN 'FUNCTION' - WHEN 'V' THEN 'VIEW' - END + - ' ' + - name + - ';' + SELECT 10, FullName, ItemType + FROM ConstraintInfo + UNION ALL + SELECT 20, FullName, ItemType FROM ObjectInfo UNION ALL - SELECT 20, - 'DROP TYPE ' + - name + - ';' + SELECT 30, FullName, ItemType FROM TypeInfo UNION ALL - SELECT 30, - 'DROP XML SCHEMA COLLECTION ' + - name + - ';' + SELECT 40, FullName, ItemType FROM XMLSchemaInfo UNION ALL - SELECT 10000,'DROP SCHEMA ' + QUOTENAME(name) +';' - FROM sys.schemas - WHERE schema_id = SCHEMA_ID(PARSENAME(@ClassName,1)) + SELECT 10000, FullName, ItemType + FROM SchemaInfo ), StatementBlob(xml)AS ( - SELECT cmd [text()] - FROM DropStatements + SELECT GDIC.cmd [text()] + FROM DropStatements DS + CROSS APPLY tSQLt.Private_GetDropItemCmd(DS.FullName, DS.ItemType) GDIC ORDER BY no FOR XML PATH(''), TYPE ) @@ -69,6 +80,7 @@ BEGIN FROM StatementBlob; EXEC(@Cmd); +/*SnipEnd: CreateDropClassStatement.ps1*/ END; ---Build- GO diff --git a/Source/tSQLt.EnableExternalAccess.ssp.sql b/Source/tSQLt.EnableExternalAccess.ssp.sql index 0955c988b..bab5b8f59 100644 --- a/Source/tSQLt.EnableExternalAccess.ssp.sql +++ b/Source/tSQLt.EnableExternalAccess.ssp.sql @@ -7,6 +7,14 @@ CREATE PROCEDURE tSQLt.EnableExternalAccess @enable BIT = 1 AS BEGIN + IF((SELECT HostPlatform FROM tSQLt.Info()) = 'Linux') + BEGIN + IF(@try = 0) + BEGIN + RAISERROR('tSQLt.EnableExternalAccess is not supported on Linux.',16,10); + END; + RETURN -1; + END; BEGIN TRY IF @enable = 1 BEGIN diff --git a/Source/tSQLt.Fail.ssp.sql b/Source/tSQLt.Fail.ssp.sql index 6153c1e42..8eb493704 100644 --- a/Source/tSQLt.Fail.ssp.sql +++ b/Source/tSQLt.Fail.ssp.sql @@ -37,7 +37,7 @@ BEGIN SAVE TRAN @TranName; END; - INSERT INTO tSQLt.TestMessage(Msg) + INSERT INTO #TestMessage(Msg) SELECT COALESCE(@Message0, '!NULL!') + COALESCE(@Message1, '!NULL!') + COALESCE(@Message2, '!NULL!') diff --git a/Source/tSQLt.FakeFunction.ssp.sql b/Source/tSQLt.FakeFunction.ssp.sql index 0514bfe7d..09b25b5cd 100644 --- a/Source/tSQLt.FakeFunction.ssp.sql +++ b/Source/tSQLt.FakeFunction.ssp.sql @@ -3,29 +3,41 @@ GO ---Build+ GO CREATE PROCEDURE tSQLt.FakeFunction - @FunctionName NVARCHAR(MAX), - @FakeFunctionName NVARCHAR(MAX) + @FunctionName NVARCHAR(MAX), + @FakeFunctionName NVARCHAR(MAX) = NULL, + @FakeDataSource NVARCHAR(MAX) = NULL + AS BEGIN DECLARE @FunctionObjectId INT; DECLARE @FakeFunctionObjectId INT; DECLARE @IsScalarFunction BIT; + DECLARE @NewNameOfOriginalFunction NVARCHAR(MAX); EXEC tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction @FunctionName = @FunctionName, @FakeFunctionName = @FakeFunctionName, + @FakeDataSource = @FakeDataSource, @FunctionObjectId = @FunctionObjectId OUT, @FakeFunctionObjectId = @FakeFunctionObjectId OUT, @IsScalarFunction = @IsScalarFunction OUT; - EXEC tSQLt.RemoveObject @ObjectName = @FunctionName; + EXEC tSQLt.RemoveObject + @ObjectName = @FunctionName, + @NewName = @NewNameOfOriginalFunction OUTPUT; EXEC tSQLt.Private_CreateFakeFunction @FunctionName = @FunctionName, @FakeFunctionName = @FakeFunctionName, + @FakeDataSource = @FakeDataSource, @FunctionObjectId = @FunctionObjectId, @FakeFunctionObjectId = @FakeFunctionObjectId, @IsScalarFunction = @IsScalarFunction; + EXEC tSQLt.Private_MarktSQLtTempObject + @ObjectName = @FunctionName, + @ObjectType = N'FUNCTION', + @NewNameOfOriginalObject = @NewNameOfOriginalFunction; + END; GO diff --git a/Source/tSQLt.FakeTable.ssp.sql b/Source/tSQLt.FakeTable.ssp.sql index a2635edba..95790ea13 100644 --- a/Source/tSQLt.FakeTable.ssp.sql +++ b/Source/tSQLt.FakeTable.ssp.sql @@ -9,47 +9,52 @@ CREATE PROCEDURE tSQLt.FakeTable @Defaults BIT = NULL AS BEGIN - DECLARE @OrigSchemaName NVARCHAR(MAX); - DECLARE @OrigTableName NVARCHAR(MAX); - DECLARE @NewNameOfOriginalTable NVARCHAR(4000); - DECLARE @OrigTableFullName NVARCHAR(MAX); SET @OrigTableFullName = NULL; - - SELECT @OrigSchemaName = @SchemaName, - @OrigTableName = @TableName - - IF(@OrigTableName NOT IN (PARSENAME(@OrigTableName,1),QUOTENAME(PARSENAME(@OrigTableName,1))) - AND @OrigSchemaName IS NOT NULL) + DECLARE @OrigObjectCleanQuotedSchemaName NVARCHAR(MAX); + DECLARE @OrigObjectCleanQuotedName NVARCHAR(MAX); + DECLARE @OrigObjectNewName NVARCHAR(4000); + DECLARE @OrigObjectFullName NVARCHAR(MAX) = NULL; + DECLARE @TargetObjectFullName NVARCHAR(MAX) = NULL; + DECLARE @OriginalObjectObjectId INT; + DECLARE @TargetObjectObjectId INT; + + IF(@TableName NOT IN (PARSENAME(@TableName,1),QUOTENAME(PARSENAME(@TableName,1))) + AND @SchemaName IS NOT NULL) BEGIN RAISERROR('When @TableName is a multi-part identifier, @SchemaName must be NULL!',16,10); END - SELECT @SchemaName = CleanSchemaName, - @TableName = CleanTableName + SELECT @OrigObjectCleanQuotedSchemaName = CleanSchemaName, + @OrigObjectCleanQuotedName = CleanTableName FROM tSQLt.Private_ResolveFakeTableNamesForBackwardCompatibility(@TableName, @SchemaName); - EXEC tSQLt.Private_ValidateFakeTableParameters @SchemaName,@OrigTableName,@OrigSchemaName; + EXEC tSQLt.Private_ValidateFakeTableParameters @OrigObjectCleanQuotedSchemaName,@TableName,@SchemaName; + + SET @OrigObjectFullName = @OrigObjectCleanQuotedSchemaName + '.' + @OrigObjectCleanQuotedName; + + EXEC tSQLt.Private_RenameObjectToUniqueName @OrigObjectCleanQuotedSchemaName, @OrigObjectCleanQuotedName, @OrigObjectNewName OUTPUT; - EXEC tSQLt.Private_RenameObjectToUniqueName @SchemaName, @TableName, @NewNameOfOriginalTable OUTPUT; + SET @OriginalObjectObjectId = OBJECT_ID(@OrigObjectCleanQuotedSchemaName + '.' + QUOTENAME(@OrigObjectNewName)); - SELECT @OrigTableFullName = S.base_object_name + SELECT @TargetObjectFullName = S.base_object_name FROM sys.synonyms AS S - WHERE S.object_id = OBJECT_ID(@SchemaName + '.' + @NewNameOfOriginalTable); + WHERE S.object_id = @OriginalObjectObjectId; - IF(@OrigTableFullName IS NOT NULL) + IF(@TargetObjectFullName IS NOT NULL) BEGIN - IF(COALESCE(OBJECT_ID(@OrigTableFullName,'U'),OBJECT_ID(@OrigTableFullName,'V')) IS NULL) + IF(COALESCE(OBJECT_ID(@TargetObjectFullName,'U'),OBJECT_ID(@TargetObjectFullName,'V')) IS NULL) BEGIN - RAISERROR('Cannot fake synonym %s.%s as it is pointing to %s, which is not a table or view!',16,10,@SchemaName,@TableName,@OrigTableFullName); + RAISERROR('Cannot fake synonym %s as it is pointing to %s, which is not a table or view!',16,10,@OrigObjectFullName,@TargetObjectFullName); END; + SET @TargetObjectObjectId = OBJECT_ID(@TargetObjectFullName); END; ELSE BEGIN - SET @OrigTableFullName = @SchemaName + '.' + @NewNameOfOriginalTable; + SET @TargetObjectObjectId = @OriginalObjectObjectId; END; - EXEC tSQLt.Private_CreateFakeOfTable @SchemaName, @TableName, @OrigTableFullName, @Identity, @ComputedColumns, @Defaults; + EXEC tSQLt.Private_CreateFakeOfTable @OrigObjectCleanQuotedSchemaName, @OrigObjectCleanQuotedName, @TargetObjectObjectId, @Identity, @ComputedColumns, @Defaults; - EXEC tSQLt.Private_MarkFakeTable @SchemaName, @TableName, @NewNameOfOriginalTable; + EXEC tSQLt.Private_MarktSQLtTempObject @OrigObjectFullName, N'TABLE', @OrigObjectNewName; END ---Build- GO diff --git a/Source/tSQLt.FriendlySQLServerVersion.sfn.sql b/Source/tSQLt.FriendlySQLServerVersion.sfn.sql new file mode 100644 index 000000000..052051a0c --- /dev/null +++ b/Source/tSQLt.FriendlySQLServerVersion.sfn.sql @@ -0,0 +1,29 @@ +IF OBJECT_ID('tSQLt.FriendlySQLServerVersion') IS NOT NULL DROP FUNCTION tSQLt.FriendlySQLServerVersion; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.FriendlySQLServerVersion(@ProductVersion NVARCHAR(128)) +/* Important: Do not rename the @ProductVersion parameter! */ +RETURNS TABLE +AS +RETURN +/* Important: Do not rename the @ProductVersion parameter! */ +/*StartSnip*/ + SELECT + @ProductVersion ProductVersion, + CASE + WHEN SSV.Major = '16' THEN '2022' + WHEN SSV.Major = '15' THEN '2019' + WHEN SSV.Major = '14' THEN '2017' + WHEN SSV.Major = '13' THEN '2016' + WHEN SSV.Major = '12' THEN '2014' + WHEN SSV.Major = '11' THEN '2012' + WHEN SSV.Major = '10' AND SSV.Minor IN ('50','5') THEN '2008R2' + WHEN SSV.Major = '10' AND SSV.Minor IN ('00','0') THEN '2008' + END FriendlyVersion +/*EndSnip*/ +/* Important: Do not rename the @ProductVersion parameter! */ + FROM tSQLt.Private_SplitSqlVersion(@ProductVersion) AS SSV; +GO +---Build- +GO \ No newline at end of file diff --git a/Source/tSQLt.Info.sfn.sql b/Source/tSQLt.Info.sfn.sql index bf49c1115..5f2421db1 100644 --- a/Source/tSQLt.Info.sfn.sql +++ b/Source/tSQLt.Info.sfn.sql @@ -1,6 +1,8 @@ IF OBJECT_ID('tSQLt.Info') IS NOT NULL DROP FUNCTION tSQLt.Info; +-- This will not work if executed outside of the build! GO ---Build+ +GO CREATE FUNCTION tSQLt.Info() RETURNS TABLE AS @@ -8,22 +10,22 @@ RETURN SELECT Version = '$LATEST-BUILD-NUMBER$', ClrVersion = (SELECT tSQLt.Private::Info()), ClrSigningKey = (SELECT tSQLt.Private::SigningKey()), + InstalledOnSqlVersion = (SELECT SqlVersion FROM tSQLt.Private_InstallationInfo()), V.SqlVersion, V.SqlBuild, - V.SqlEdition + V.SqlEdition, + V.HostPlatform FROM ( - SELECT CAST(VI.major+'.'+VI.minor AS NUMERIC(10,2)) AS SqlVersion, - CAST(VI.build+'.'+VI.revision AS NUMERIC(10,2)) AS SqlBuild, - SqlEdition - FROM - ( - SELECT PARSENAME(PSV.ProductVersion,4) major, - PARSENAME(PSV.ProductVersion,3) minor, - PARSENAME(PSV.ProductVersion,2) build, - PARSENAME(PSV.ProductVersion,1) revision, - Edition AS SqlEdition + SELECT CAST(PSSV.Major+'.'+PSSV.Minor AS NUMERIC(10,2)) AS SqlVersion, + CAST(PSSV.Build+'.'+PSSV.Revision AS NUMERIC(10,2)) AS SqlBuild, + PSV.Edition AS SqlEdition, + PHP.host_platform AS HostPlatform FROM tSQLt.Private_SqlVersion() AS PSV - )VI + CROSS APPLY tSQLt.Private_SplitSqlVersion(PSV.ProductVersion) AS PSSV + CROSS JOIN tSQLt.Private_HostPlatform AS PHP )V; +GO ---Build- +GO +SELECT * FROM tSQLt.Info() AS I; diff --git a/Source/tSQLt.InstallAssemblyKey.ssp.sql b/Source/tSQLt.InstallAssemblyKey.ssp.sql new file mode 100644 index 000000000..7604c00eb --- /dev/null +++ b/Source/tSQLt.InstallAssemblyKey.ssp.sql @@ -0,0 +1,97 @@ +IF OBJECT_ID('tSQLt.InstallAssemblyKey') IS NOT NULL DROP PROCEDURE tSQLt.InstallAssemblyKey; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.InstallAssemblyKey +AS +BEGIN + IF(NOT EXISTS(SELECT * FROM sys.fn_my_permissions(NULL,'server') AS FMP WHERE FMP.permission_name = 'CONTROL SERVER')) + BEGIN + RAISERROR('Only principals with CONTROL SERVER permission can execute this procedure.',16,10); + RETURN -1; + END; + + DECLARE @cmd NVARCHAR(MAX); + DECLARE @cmd2 NVARCHAR(MAX); + DECLARE @master_sys_sp_executesql NVARCHAR(MAX); SET @master_sys_sp_executesql = 'master.sys.sp_executesql'; + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + + DECLARE @AssemblyKeyBytes VARBINARY(MAX), + @AssemblyKeyThumbPrint VARBINARY(MAX); + + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes OUT, @AssemblyKeyThumbPrint OUT; + + SET @cmd = 'IF EXISTS(SELECT * FROM sys.assemblies WHERE name = ''tSQLtAssemblyKey'') DROP ASSEMBLY tSQLtAssemblyKey;'; + EXEC @master_sys_sp_executesql @cmd; + + SET @cmd2 = 'SELECT @cmd = ''DROP ASSEMBLY ''+QUOTENAME(A.name)+'';'''+ + ' FROM master.sys.assemblies AS A'+ + ' WHERE A.clr_name LIKE ''tsqltassemblykey, %'';'; + EXEC sys.sp_executesql @cmd2,N'@cmd NVARCHAR(MAX) OUTPUT',@cmd OUT; + EXEC @master_sys_sp_executesql @cmd; + + DECLARE @Hash VARBINARY(64) = NULL; + IF(@ProductMajorVersion>=14) + BEGIN + SELECT @Hash = HASHBYTES('SHA2_512',@AssemblyKeyBytes); + + SELECT @cmd = + 'IF NOT EXISTS (SELECT * FROM sys.trusted_assemblies WHERE [hash] = @Hash)'+ + 'BEGIN'+ + ' EXEC sys.sp_add_trusted_assembly @hash = @Hash, @description = N''tSQLt Ephemeral'';'+ + 'END ELSE BEGIN'+ + ' SELECT @Hash = NULL FROM sys.trusted_assemblies WHERE [hash] = @Hash AND description <> ''tSQLt Ephemeral'';'+ + 'END;'; + EXEC @master_sys_sp_executesql @cmd, N'@Hash VARBINARY(64) OUTPUT',@Hash OUT; + END; + + SELECT @cmd = + 'CREATE ASSEMBLY tSQLtAssemblyKey AUTHORIZATION dbo FROM ' + + CONVERT(NVARCHAR(MAX),@AssemblyKeyBytes,1) + + ' WITH PERMISSION_SET = SAFE;' + EXEC @master_sys_sp_executesql @cmd; + + IF SUSER_ID('tSQLtAssemblyKey') IS NOT NULL DROP LOGIN tSQLtAssemblyKey; + + SET @cmd = N'IF ASYMKEY_ID(''tSQLtAssemblyKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtAssemblyKey;'; + EXEC @master_sys_sp_executesql @cmd; + + SET @cmd2 = 'SELECT @cmd = ISNULL(''DROP LOGIN ''+QUOTENAME(SP.name)+'';'','''')+''DROP ASYMMETRIC KEY '' + QUOTENAME(AK.name) + '';'''+ + ' FROM master.sys.asymmetric_keys AS AK'+ + ' LEFT JOIN master.sys.server_principals AS SP'+ + ' ON AK.sid = SP.sid'+ + ' WHERE AK.thumbprint = @AssemblyKeyThumbPrint;'; + EXEC sys.sp_executesql @cmd2,N'@cmd NVARCHAR(MAX) OUTPUT, @AssemblyKeyThumbPrint VARBINARY(MAX)',@cmd OUT, @AssemblyKeyThumbPrint; + EXEC @master_sys_sp_executesql @cmd; + + SET @cmd = 'CREATE ASYMMETRIC KEY tSQLtAssemblyKey FROM ASSEMBLY tSQLtAssemblyKey;'; + EXEC @master_sys_sp_executesql @cmd; + + SET @cmd = 'CREATE LOGIN tSQLtAssemblyKey FROM ASYMMETRIC KEY tSQLtAssemblyKey;'; + EXEC @master_sys_sp_executesql @cmd; + + SET @cmd = 'DROP ASSEMBLY tSQLtAssemblyKey;'; + EXEC @master_sys_sp_executesql @cmd; + + IF(@Hash IS NOT NULL) + BEGIN + SELECT @cmd = 'EXEC sys.sp_drop_trusted_assembly @hash = @Hash;'; + EXEC @master_sys_sp_executesql @cmd, N'@Hash VARBINARY(64)',@Hash; + END; + + IF(@ProductMajorVersion>=14) + BEGIN + SET @cmd = 'GRANT UNSAFE ASSEMBLY TO tSQLtAssemblyKey;'; + END + ELSE + BEGIN + SET @cmd = 'GRANT EXTERNAL ACCESS ASSEMBLY TO tSQLtAssemblyKey;'; + END; + + EXEC @master_sys_sp_executesql @cmd; + +END; +GO +---Build- +GO \ No newline at end of file diff --git a/Source/tSQLt.InstallExternalAccessKey.ssp.sql b/Source/tSQLt.InstallExternalAccessKey.ssp.sql index f13cece0f..ba555dbda 100644 --- a/Source/tSQLt.InstallExternalAccessKey.ssp.sql +++ b/Source/tSQLt.InstallExternalAccessKey.ssp.sql @@ -5,59 +5,8 @@ GO CREATE PROCEDURE tSQLt.InstallExternalAccessKey AS BEGIN - IF(NOT EXISTS(SELECT * FROM sys.fn_my_permissions(NULL,'server') AS FMP WHERE FMP.permission_name = 'CONTROL SERVER')) - BEGIN - RAISERROR('Only principals with CONTROL SERVER permission can execute this procedure.',16,10); - RETURN -1; - END; - - DECLARE @cmd NVARCHAR(MAX); - DECLARE @cmd2 NVARCHAR(MAX); - DECLARE @master_sys_sp_executesql NVARCHAR(MAX); SET @master_sys_sp_executesql = 'master.sys.sp_executesql'; - - SET @cmd = 'IF EXISTS(SELECT * FROM sys.assemblies WHERE name = ''tSQLtExternalAccessKey'') DROP ASSEMBLY tSQLtExternalAccessKey;'; - EXEC @master_sys_sp_executesql @cmd; - - SET @cmd2 = 'SELECT @cmd = ''DROP ASSEMBLY ''+QUOTENAME(A.name)+'';'''+ - ' FROM master.sys.assemblies AS A'+ - ' WHERE A.clr_name LIKE ''tsqltexternalaccesskey, %'';'; - EXEC sys.sp_executesql @cmd2,N'@cmd NVARCHAR(MAX) OUTPUT',@cmd OUT; - EXEC @master_sys_sp_executesql @cmd; - - SELECT @cmd = - 'CREATE ASSEMBLY tSQLtExternalAccessKey AUTHORIZATION dbo FROM ' + - BH.prefix + - ' WITH PERMISSION_SET = SAFE;' - FROM tSQLt.Private_GetExternalAccessKeyBytes() AS PGEAKB - CROSS APPLY tSQLt.Private_Bin2Hex(PGEAKB.ExternalAccessKeyBytes) BH; - EXEC @master_sys_sp_executesql @cmd; - - IF SUSER_ID('tSQLtExternalAccessKey') IS NOT NULL DROP LOGIN tSQLtExternalAccessKey; - - SET @cmd = N'IF ASYMKEY_ID(''tSQLtExternalAccessKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtExternalAccessKey;'; - EXEC @master_sys_sp_executesql @cmd; - - SET @cmd2 = 'SELECT @cmd = ISNULL(''DROP LOGIN ''+QUOTENAME(SP.name)+'';'','''')+''DROP ASYMMETRIC KEY '' + QUOTENAME(AK.name) + '';'''+ - ' FROM master.sys.asymmetric_keys AS AK'+ - ' JOIN tSQLt.Private_GetExternalAccessKeyBytes() AS PGEAKB'+ - ' ON AK.thumbprint = PGEAKB.ExternalAccessKeyThumbPrint'+ - ' LEFT JOIN master.sys.server_principals AS SP'+ - ' ON AK.sid = SP.sid;'; - EXEC sys.sp_executesql @cmd2,N'@cmd NVARCHAR(MAX) OUTPUT',@cmd OUT; - EXEC @master_sys_sp_executesql @cmd; - - SET @cmd = 'CREATE ASYMMETRIC KEY tSQLtExternalAccessKey FROM ASSEMBLY tSQLtExternalAccessKey;'; - EXEC @master_sys_sp_executesql @cmd; - - SET @cmd = 'CREATE LOGIN tSQLtExternalAccessKey FROM ASYMMETRIC KEY tSQLtExternalAccessKey;'; - EXEC @master_sys_sp_executesql @cmd; - - SET @cmd = 'DROP ASSEMBLY tSQLtExternalAccessKey;'; - EXEC @master_sys_sp_executesql @cmd; - - SET @cmd = 'GRANT EXTERNAL ACCESS ASSEMBLY TO tSQLtExternalAccessKey;'; - EXEC @master_sys_sp_executesql @cmd; - + EXEC tSQLt.Private_Print @Message='tSQLt.InstallExternalAccessKey is deprecated. Please use tSQLt.InstallAssemblyKey instead.'; + EXEC tSQLt.InstallAssemblyKey; END; GO ---Build- diff --git a/Source/tSQLt.PrepareServer.ssp.sql b/Source/tSQLt.PrepareServer.ssp.sql new file mode 100644 index 000000000..d9c087002 --- /dev/null +++ b/Source/tSQLt.PrepareServer.ssp.sql @@ -0,0 +1,14 @@ +IF OBJECT_ID('tSQLt.PrepareServer') IS NOT NULL DROP PROCEDURE tSQLt.PrepareServer; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.PrepareServer +AS +BEGIN + EXEC tSQLt.Private_EnableCLR; + EXEC tSQLt.InstallAssemblyKey; +END; +GO +---Build- +GO + diff --git a/Source/tSQLt.Private_AssertNoSideEffects.ssp.sql b/Source/tSQLt.Private_AssertNoSideEffects.ssp.sql new file mode 100644 index 000000000..8febaac61 --- /dev/null +++ b/Source/tSQLt.Private_AssertNoSideEffects.ssp.sql @@ -0,0 +1,15 @@ +IF OBJECT_ID('tSQLt.Private_AssertNoSideEffects') IS NOT NULL DROP PROCEDURE tSQLt.Private_AssertNoSideEffects; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_AssertNoSideEffects + @BeforeExecutionObjectSnapshotTableName NVARCHAR(MAX), + @AfterExecutionObjectSnapshotTableName NVARCHAR(MAX), + @TestResult NVARCHAR(MAX) OUTPUT, + @TestMsg NVARCHAR(MAX) OUTPUT +AS +BEGIN + DECLARE @Command NVARCHAR(MAX) = (SELECT Command FROM tSQLt.Private_AssertNoSideEffects_GenerateCommand(@BeforeExecutionObjectSnapshotTableName, @AfterExecutionObjectSnapshotTableName)); + EXEC tSQLt.Private_CleanUpCmdHandler @CleanUpCmd=@Command, @TestResult=@TestResult OUT, @TestMsg=@TestMsg OUT; +END; +GO diff --git a/Source/tSQLt.Private_AssertNoSideEffects_GenerateCommand.sfn.sql b/Source/tSQLt.Private_AssertNoSideEffects_GenerateCommand.sfn.sql new file mode 100644 index 000000000..aa179eeb3 --- /dev/null +++ b/Source/tSQLt.Private_AssertNoSideEffects_GenerateCommand.sfn.sql @@ -0,0 +1,25 @@ +IF OBJECT_ID('tSQLt.Private_AssertNoSideEffects_GenerateCommand') IS NOT NULL DROP FUNCTION tSQLt.Private_AssertNoSideEffects_GenerateCommand; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_AssertNoSideEffects_GenerateCommand( + @BeforeExecutionObjectSnapshotTableName NVARCHAR(MAX), + @AfterExecutionObjectSnapshotTableName NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT ' + SELECT * INTO #ObjectDiscrepancies + FROM( + (SELECT ''Deleted'' [Status], B.* FROM '+@BeforeExecutionObjectSnapshotTableName+' AS B EXCEPT SELECT ''Deleted'' [Status],* FROM '+@AfterExecutionObjectSnapshotTableName+' AS A) + UNION ALL + (SELECT ''Added'' [Status], A.* FROM '+@AfterExecutionObjectSnapshotTableName+' AS A EXCEPT SELECT ''Added'' [Status], * FROM '+@BeforeExecutionObjectSnapshotTableName+' AS B) + )D; + IF(EXISTS(SELECT 1 FROM #ObjectDiscrepancies)) + BEGIN + DECLARE @TableToText NVARCHAR(MAX); + EXEC tSQLt.TableToText @TableName = ''#ObjectDiscrepancies'' ,@txt = @TableToText OUTPUT, @OrderBy = ''[Status] ASC, SchemaName ASC, ObjectName ASC''; + RAISERROR(''After the test executed, there were unexpected or missing objects in the database: %s'',16,10,@TableToText); + END;' Command; +GO diff --git a/Source/tSQLt.Private_Bin2Hex.sfn.sql b/Source/tSQLt.Private_Bin2Hex.sfn.sql deleted file mode 100644 index 029d7813c..000000000 --- a/Source/tSQLt.Private_Bin2Hex.sfn.sql +++ /dev/null @@ -1,13 +0,0 @@ -IF OBJECT_ID('tSQLt.Private_Bin2Hex') IS NOT NULL DROP FUNCTION tSQLt.Private_Bin2Hex; -GO ----Build+ -GO -CREATE FUNCTION tSQLt.Private_Bin2Hex(@vb VARBINARY(MAX)) -RETURNS TABLE -AS -RETURN - SELECT X.S AS bare, '0x'+X.S AS prefix - FROM (SELECT LOWER(CAST('' AS XML).value('xs:hexBinary(sql:variable("@vb") )','VARCHAR(MAX)')))X(S); -GO ----Build- -GO diff --git a/Source/tSQLt.Private_CleanUp.ssp.sql b/Source/tSQLt.Private_CleanUp.ssp.sql new file mode 100644 index 000000000..c262958d5 --- /dev/null +++ b/Source/tSQLt.Private_CleanUp.ssp.sql @@ -0,0 +1,27 @@ +IF OBJECT_ID('tSQLt.Private_CleanUp') IS NOT NULL DROP PROCEDURE tSQLt.Private_CleanUp; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_CleanUp + @FullTestName NVARCHAR(MAX), + @Result NVARCHAR(MAX) OUTPUT, + @ErrorMsg NVARCHAR(MAX) OUTPUT +AS +BEGIN + + EXEC tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd = 'EXEC tSQLt.Private_NoTransactionHandleTables @Action=''Reset'';', + @TestResult = @Result OUT, + @TestMsg = @ErrorMsg OUT, + @ResultInCaseOfError = 'FATAL'; + + EXEC tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd = 'EXEC tSQLt.UndoTestDoubles @Force = 0;', + @TestResult = @Result OUT, + @TestMsg = @ErrorMsg OUT, + @ResultInCaseOfError = 'Abort'; + +END; +GO +---Build- +GO diff --git a/Source/tSQLt.Private_CleanUpCmdHandler.ssp.sql b/Source/tSQLt.Private_CleanUpCmdHandler.ssp.sql new file mode 100644 index 000000000..e8fdd04c0 --- /dev/null +++ b/Source/tSQLt.Private_CleanUpCmdHandler.ssp.sql @@ -0,0 +1,19 @@ +IF OBJECT_ID('tSQLt.Private_CleanUpCmdHandler') IS NOT NULL DROP PROCEDURE tSQLt.Private_CleanUpCmdHandler; +GO +---Build+ +CREATE PROCEDURE tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd NVARCHAR(MAX), + @TestResult NVARCHAR(MAX) OUTPUT, + @TestMsg NVARCHAR(MAX) OUTPUT, + @ResultInCaseOfError NVARCHAR(MAX) = 'Error' +AS +BEGIN + BEGIN TRY + EXEC(@CleanUpCmd); + END TRY + BEGIN CATCH + DECLARE @NewMsg NVARCHAR(MAX) = 'Error during clean up: (' + (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()) + ')'; + SELECT @TestMsg = Message, @TestResult = Result FROM tSQLt.Private_HandleMessageAndResult(@TestMsg /*PrevMsg*/, @TestResult /*PrevResult*/, @NewMsg /*NewMsg*/, @ResultInCaseOfError /*NewResult*/); + END CATCH; +END; +GO diff --git a/Source/tSQLt.Private_CreateFakeFunction.ssp.sql b/Source/tSQLt.Private_CreateFakeFunction.ssp.sql index 389276d24..41f601d91 100644 --- a/Source/tSQLt.Private_CreateFakeFunction.ssp.sql +++ b/Source/tSQLt.Private_CreateFakeFunction.ssp.sql @@ -3,11 +3,12 @@ GO ---Build+ GO CREATE PROCEDURE tSQLt.Private_CreateFakeFunction - @FunctionName NVARCHAR(MAX), - @FakeFunctionName NVARCHAR(MAX), - @FunctionObjectId INT, - @FakeFunctionObjectId INT, - @IsScalarFunction BIT + @FunctionName NVARCHAR(MAX), + @FakeFunctionName NVARCHAR(MAX) = NULL, + @FunctionObjectId INT = NULL, + @FakeFunctionObjectId INT = NULL, + @IsScalarFunction BIT = NULL, + @FakeDataSource NVARCHAR(MAX) = NULL AS BEGIN DECLARE @ReturnType NVARCHAR(MAX); @@ -44,9 +45,24 @@ BEGIN BEGIN EXEC('CREATE FUNCTION '+@FunctionName+'('+@ParameterList+') RETURNS '+@ReturnType+' AS BEGIN RETURN '+@FakeFunctionName+'('+@ParameterCallList+');END;'); END - ELSE + ELSE BEGIN - EXEC('CREATE FUNCTION '+@FunctionName+'('+@ParameterList+') RETURNS TABLE AS RETURN SELECT * FROM '+@FakeFunctionName+'('+@ParameterCallList+');'); + DECLARE @cmd NVARCHAR(MAX); + IF (@FakeDataSource IS NOT NULL) + BEGIN + SET @cmd = + CASE + WHEN OBJECT_ID(@FakeDataSource) IS NOT NULL THEN 'SELECT * FROM '+@FakeDataSource + WHEN @FakeDataSource LIKE '(%)%(%)' THEN 'SELECT * FROM '+@FakeDataSource + ELSE @FakeDataSource + END; + END + ELSE + BEGIN + SET @cmd = 'SELECT * FROM '+@FakeFunctionName+'('+@ParameterCallList+')'; + END; + SET @cmd = 'CREATE FUNCTION '+@FunctionName+'('+@ParameterList+') RETURNS TABLE AS RETURN '+@cmd+';' + EXEC(@cmd); END; END; GO diff --git a/Source/tSQLt.Private_CreateFakeFunctionStatement.sfn.sql b/Source/tSQLt.Private_CreateFakeFunctionStatement.sfn.sql new file mode 100644 index 000000000..d9575bada --- /dev/null +++ b/Source/tSQLt.Private_CreateFakeFunctionStatement.sfn.sql @@ -0,0 +1,90 @@ +IF OBJECT_ID('tSQLt.Private_CreateFakeFunctionStatement') IS NOT NULL DROP FUNCTION tSQLt.Private_CreateFakeFunctionStatement; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_CreateFakeFunctionStatement( + @FunctionObjectId INT, + @ReturnValue NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT + 'CREATE FUNCTION ' + QUOTENAME(OBJECT_SCHEMA_NAME(@FunctionObjectId)) + '.' + QUOTENAME(OBJECT_NAME(@FunctionObjectId)) + + '(' + + ISNULL(PAS.ParametersAndReturnScalar, '') + + ISNULL(') RETURNS TABLE AS RETURN ' + T.TypeOnlySelectStatement,'') + ';' CreateStatement, + T.TypeOnlySelectStatement + FROM + ( + SELECT + ( + SELECT + CASE P.is_output + WHEN 0 THEN CASE WHEN P._RN_ = 1 THEN '' ELSE ',' END +P.name+' '+T.TypeName + WHEN 1 THEN ') RETURNS '+T.TypeName+' AS BEGIN RETURN CAST('+ISNULL(''''+@ReturnValue+'''','NULL')+' AS '+T.TypeName+'); END' + END + FROM + ( + SELECT + ROW_NUMBER()OVER(ORDER BY PP.is_output ASC,PP.parameter_id ASC) _RN_, + PP.* + FROM + ( + SELECT + object_id, + name, + parameter_id, + system_type_id, + user_type_id, + max_length, + precision, + scale, + is_output + FROM sys.parameters + ) AS PP + WHERE PP.object_id = @FunctionObjectId + )AS P + CROSS APPLY tSQLt.Private_GetFullTypeName(P.user_type_id,P.max_length,P.precision,P.scale,NULL) AS T + ORDER BY P._RN_ + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') ParametersAndReturnScalar + )PAS + CROSS JOIN + ( + SELECT + ( + SELECT + CASE WHEN P.column_id = 1 + THEN 'SELECT TOP(0) ' + ELSE ',' + END + + 'CAST(NULL AS '+T.TypeName+') AS '+QUOTENAME(P.name) + FROM + ( + SELECT + ROW_NUMBER()OVER(ORDER BY PP.column_id ASC) _RN_, + PP.* + FROM + ( + SELECT + object_id, + name, + column_id, + system_type_id, + user_type_id, + max_length, + precision, + scale + FROM sys.columns + ) AS PP + WHERE PP.object_id = @FunctionObjectId + )AS P + CROSS APPLY tSQLt.Private_GetFullTypeName(P.user_type_id,P.max_length,P.precision,P.scale,NULL) AS T + ORDER BY P._RN_ + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') TypeOnlySelectStatement + )T +GO +---Build- +GO diff --git a/Source/tSQLt.Private_CreateFakeOfTable.ssp.sql b/Source/tSQLt.Private_CreateFakeOfTable.ssp.sql index af732fd63..625d910b8 100644 --- a/Source/tSQLt.Private_CreateFakeOfTable.ssp.sql +++ b/Source/tSQLt.Private_CreateFakeOfTable.ssp.sql @@ -4,39 +4,16 @@ GO CREATE PROCEDURE tSQLt.Private_CreateFakeOfTable @SchemaName NVARCHAR(MAX), @TableName NVARCHAR(MAX), - @OrigTableFullName NVARCHAR(MAX), + @OrigTableObjectId INT, @Identity BIT, @ComputedColumns BIT, @Defaults BIT AS BEGIN - DECLARE @Cmd NVARCHAR(MAX); - DECLARE @Cols NVARCHAR(MAX); - - SELECT @Cols = - ( - SELECT - ',' + - QUOTENAME(name) + - cc.ColumnDefinition + - dc.DefaultDefinition + - id.IdentityDefinition + - CASE WHEN cc.IsComputedColumn = 1 OR id.IsIdentityColumn = 1 - THEN '' - ELSE ' NULL' - END - FROM sys.columns c - CROSS APPLY tSQLt.Private_GetDataTypeOrComputedColumnDefinition(c.user_type_id, c.max_length, c.precision, c.scale, c.collation_name, c.object_id, c.column_id, @ComputedColumns) cc - CROSS APPLY tSQLt.Private_GetDefaultConstraintDefinition(c.object_id, c.column_id, @Defaults) AS dc - CROSS APPLY tSQLt.Private_GetIdentityDefinition(c.object_id, c.column_id, @Identity) AS id - WHERE object_id = OBJECT_ID(@OrigTableFullName) - ORDER BY column_id - FOR XML PATH(''), TYPE - ).value('.', 'NVARCHAR(MAX)'); - - SELECT @Cmd = 'CREATE TABLE ' + @SchemaName + '.' + @TableName + '(' + STUFF(@Cols,1,1,'') + ')'; - - EXEC (@Cmd); + DECLARE @cmd NVARCHAR(MAX) = + (SELECT CreateTableStatement + FROM tSQLt.Private_CreateFakeTableStatement(@OrigTableObjectId, @SchemaName+'.'+@TableName,@Identity,@ComputedColumns,@Defaults,0)); + EXEC (@cmd); END; ---Build- GO diff --git a/Source/tSQLt.Private_CreateFakeTableStatement.sfn.sql b/Source/tSQLt.Private_CreateFakeTableStatement.sfn.sql new file mode 100644 index 000000000..7e3d17c58 --- /dev/null +++ b/Source/tSQLt.Private_CreateFakeTableStatement.sfn.sql @@ -0,0 +1,44 @@ +IF OBJECT_ID('tSQLt.Private_CreateFakeTableStatement') IS NOT NULL DROP FUNCTION tSQLt.Private_CreateFakeTableStatement; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_CreateFakeTableStatement( + @OriginalTableObjectId INT, + @FullFakeTableName NVARCHAR(MAX), + @Identity BIT, + @ComputedColumns BIT, + @Defaults BIT, + @PreserveNOTNULL BIT +) +RETURNS TABLE +AS +RETURN + SELECT + 'CREATE TABLE ' + @FullFakeTableName + '(' + STUFF(Cols,1,1,'') + ')' CreateTableStatement, + 'CREATE TYPE ' + @FullFakeTableName + ' AS TABLE(' + STUFF(Cols,1,1,'') + ')' CreateTableTypeStatement + FROM + ( + SELECT + ( + SELECT + ',' + + QUOTENAME(name) + + cc.ColumnDefinition + + dc.DefaultDefinition + + id.IdentityDefinition + + CASE WHEN cc.IsComputedColumn = 1 OR id.IsIdentityColumn = 1 + THEN '' + ELSE CASE WHEN @PreserveNOTNULL = 1 AND c.is_nullable = 0 THEN ' NOT NULL' ELSE ' NULL' END + END + FROM sys.columns c + CROSS APPLY tSQLt.Private_GetDataTypeOrComputedColumnDefinition(c.user_type_id, c.max_length, c.precision, c.scale, c.collation_name, c.object_id, c.column_id, @ComputedColumns) cc + CROSS APPLY tSQLt.Private_GetDefaultConstraintDefinition(c.object_id, c.column_id, @Defaults) AS dc + CROSS APPLY tSQLt.Private_GetIdentityDefinition(c.object_id, c.column_id, @Identity) AS id + WHERE object_id = @OriginalTableObjectId + ORDER BY column_id + FOR XML PATH(''), TYPE + ).value('.', 'NVARCHAR(MAX)') + ) AS X(Cols); +GO +---Build- +GO diff --git a/Source/tSQLt.Private_CreateProcedureSpy.ssp.sql b/Source/tSQLt.Private_CreateProcedureSpy.ssp.sql index 5a839a57b..fa4fd46a8 100644 --- a/Source/tSQLt.Private_CreateProcedureSpy.ssp.sql +++ b/Source/tSQLt.Private_CreateProcedureSpy.ssp.sql @@ -8,91 +8,8 @@ CREATE PROCEDURE tSQLt.Private_CreateProcedureSpy @CommandToExecute NVARCHAR(MAX) = NULL AS BEGIN - DECLARE @Cmd NVARCHAR(MAX); - DECLARE @ProcParmList NVARCHAR(MAX), - @TableColList NVARCHAR(MAX), - @ProcParmTypeList NVARCHAR(MAX), - @TableColTypeList NVARCHAR(MAX); - - DECLARE @Seperator CHAR(1), - @ProcParmTypeListSeparater CHAR(1), - @ParamName sysname, - @TypeName sysname, - @IsOutput BIT, - @IsCursorRef BIT, - @IsTableType BIT; - - - SELECT @Seperator = '', @ProcParmTypeListSeparater = '', - @ProcParmList = '', @TableColList = '', @ProcParmTypeList = '', @TableColTypeList = ''; - - DECLARE Parameters CURSOR FOR - SELECT p.name, t.TypeName, p.is_output, p.is_cursor_ref, t.IsTableType - FROM sys.parameters p - CROSS APPLY tSQLt.Private_GetFullTypeName(p.user_type_id,p.max_length,p.precision,p.scale,NULL) t - WHERE object_id = @ProcedureObjectId; - - OPEN Parameters; - - FETCH NEXT FROM Parameters INTO @ParamName, @TypeName, @IsOutput, @IsCursorRef, @IsTableType; - WHILE (@@FETCH_STATUS = 0) - BEGIN - IF @IsCursorRef = 0 - BEGIN - SELECT @ProcParmList = @ProcParmList + @Seperator + - CASE WHEN @IsTableType = 1 - THEN '(SELECT * FROM '+@ParamName+' FOR XML PATH(''row''),TYPE,ROOT('''+STUFF(@ParamName,1,1,'')+'''))' - ELSE @ParamName - END, - @TableColList = @TableColList + @Seperator + '[' + STUFF(@ParamName,1,1,'') + ']', - @ProcParmTypeList = @ProcParmTypeList + @ProcParmTypeListSeparater + @ParamName + ' ' + @TypeName + - CASE WHEN @IsTableType = 1 THEN ' READONLY' ELSE ' = NULL ' END+ - CASE WHEN @IsOutput = 1 THEN ' OUT' ELSE '' END, - @TableColTypeList = @TableColTypeList + ',[' + STUFF(@ParamName,1,1,'') + '] ' + - CASE - WHEN @IsTableType = 1 - THEN 'XML' - WHEN @TypeName LIKE '%nchar%' - OR @TypeName LIKE '%nvarchar%' - THEN 'NVARCHAR(MAX)' - WHEN @TypeName LIKE '%char%' - THEN 'VARCHAR(MAX)' - ELSE @TypeName - END + ' NULL'; - - SELECT @Seperator = ','; - SELECT @ProcParmTypeListSeparater = ','; - END - ELSE - BEGIN - SELECT @ProcParmTypeList = @ProcParmTypeListSeparater + @ParamName + ' CURSOR VARYING OUTPUT'; - SELECT @ProcParmTypeListSeparater = ','; - END; - - FETCH NEXT FROM Parameters INTO @ParamName, @TypeName, @IsOutput, @IsCursorRef, @IsTableType; - END; - - CLOSE Parameters; - DEALLOCATE Parameters; - - DECLARE @InsertStmt NVARCHAR(MAX); - SELECT @InsertStmt = 'INSERT INTO ' + @LogTableName + - CASE WHEN @TableColList = '' THEN ' DEFAULT VALUES' - ELSE ' (' + @TableColList + ') SELECT ' + @ProcParmList - END + ';'; - - SELECT @Cmd = 'CREATE TABLE ' + @LogTableName + ' (_id_ int IDENTITY(1,1) PRIMARY KEY CLUSTERED ' + @TableColTypeList + ');'; - EXEC(@Cmd); - - SELECT @Cmd = 'CREATE PROCEDURE ' + @OriginalProcedureName + ' ' + @ProcParmTypeList + - ' AS BEGIN ' + - @InsertStmt + - ISNULL(@CommandToExecute, '') + ';' + - ' END;'; - EXEC(@Cmd); - - RETURN 0; + RETURN; END; ---Build- GO diff --git a/Source/tSQLt.Private_CreateResultTableForCompareTables.ssp.sql b/Source/tSQLt.Private_CreateResultTableForCompareTables.ssp.sql index 17055733f..5f629bab2 100644 --- a/Source/tSQLt.Private_CreateResultTableForCompareTables.ssp.sql +++ b/Source/tSQLt.Private_CreateResultTableForCompareTables.ssp.sql @@ -10,11 +10,10 @@ AS BEGIN DECLARE @Cmd NVARCHAR(MAX); SET @Cmd = ' - SELECT ''='' AS ' + @ResultColumn + ', Expected.* INTO ' + @ResultTable + ' - FROM tSQLt.Private_NullCellTable N - LEFT JOIN ' + @BaseTable + ' AS Expected ON N.I <> N.I - TRUNCATE TABLE ' + @ResultTable + ';' --Need to insert an actual row to prevent IDENTITY property from transfering (IDENTITY_COL can't be NULLable); + SELECT TOP(0) ''>'' AS ' + @ResultColumn + ', Expected.* INTO ' + @ResultTable + ' + FROM ' + @BaseTable + ' AS Expected RIGHT JOIN ' + @BaseTable + ' AS X ON 1=0; ' EXEC(@Cmd); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = @ResultTable, @ObjectType = N'TABLE'; END GO ---Build- diff --git a/Source/tSQLt.Private_EnableCLR.ssp.sql b/Source/tSQLt.Private_EnableCLR.ssp.sql new file mode 100644 index 000000000..c0d2fea3b --- /dev/null +++ b/Source/tSQLt.Private_EnableCLR.ssp.sql @@ -0,0 +1,14 @@ +IF OBJECT_ID('tSQLt.Private_EnableCLR') IS NOT NULL DROP PROCEDURE tSQLt.Private_EnableCLR; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_EnableCLR +AS +BEGIN + EXEC master.sys.sp_configure @configname='clr enabled', @configvalue = 1; + RECONFIGURE; +END; +GO +---Build- +GO + diff --git a/Source/tSQLt.Private_GenerateCreateProcedureSpyStatement.ssp.sql b/Source/tSQLt.Private_GenerateCreateProcedureSpyStatement.ssp.sql new file mode 100644 index 000000000..6176baf30 --- /dev/null +++ b/Source/tSQLt.Private_GenerateCreateProcedureSpyStatement.ssp.sql @@ -0,0 +1,110 @@ +IF OBJECT_ID('tSQLt.Private_GenerateCreateProcedureSpyStatement') IS NOT NULL DROP PROCEDURE tSQLt.Private_GenerateCreateProcedureSpyStatement; +GO +---Build+ +CREATE PROCEDURE tSQLt.Private_GenerateCreateProcedureSpyStatement + @ProcedureObjectId INT, + @OriginalProcedureName NVARCHAR(MAX), + @UnquotedNewNameOfProcedure NVARCHAR(MAX) = NULL, + @LogTableName NVARCHAR(MAX), + @CommandToExecute NVARCHAR(MAX), + @CallOriginal BIT, + @CreateProcedureStatement NVARCHAR(MAX) OUTPUT, + @CreateLogTableStatement NVARCHAR(MAX) OUTPUT +AS +BEGIN + DECLARE @ProcParmListForInsert NVARCHAR(MAX) = ''; + DECLARE @ProcParmListForCall NVARCHAR(MAX) = ''; + DECLARE @TableColList NVARCHAR(MAX) = ''; + DECLARE @ProcParmTypeList NVARCHAR(MAX) = ''; + DECLARE @TableColTypeList NVARCHAR(MAX) = ''; + + DECLARE @SeparatorWithoutCursor CHAR(1) = ''; + DECLARE @SeparatorWithCursor CHAR(1) = ''; + DECLARE @ParamName sysname; + DECLARE @TypeName sysname; + DECLARE @IsOutput BIT; + DECLARE @IsCursorRef BIT; + DECLARE @IsTableType BIT; + + DECLARE Parameters CURSOR FOR + SELECT p.name, t.TypeName, p.is_output, p.is_cursor_ref, t.IsTableType + FROM sys.parameters p + CROSS APPLY tSQLt.Private_GetFullTypeName(p.user_type_id,p.max_length,p.precision,p.scale,NULL) t + WHERE object_id = @ProcedureObjectId; + + OPEN Parameters; + + FETCH NEXT FROM Parameters INTO @ParamName, @TypeName, @IsOutput, @IsCursorRef, @IsTableType; + WHILE (@@FETCH_STATUS = 0) + BEGIN + IF @IsCursorRef = 0 + BEGIN + SELECT @ProcParmListForInsert = @ProcParmListForInsert + @SeparatorWithoutCursor + + CASE WHEN @IsTableType = 1 + THEN '(SELECT * FROM '+@ParamName+' FOR XML PATH(''row''),TYPE,ROOT('''+STUFF(@ParamName,1,1,'')+'''))' + ELSE @ParamName + END, + @TableColList = @TableColList + @SeparatorWithoutCursor + '[' + STUFF(@ParamName,1,1,'') + ']', + @ProcParmTypeList = @ProcParmTypeList + @SeparatorWithCursor + @ParamName + ' ' + @TypeName + + CASE WHEN @IsTableType = 1 THEN ' READONLY' ELSE ' = NULL ' END+ + CASE WHEN @IsOutput = 1 THEN ' OUT' ELSE '' END, + @TableColTypeList = @TableColTypeList + ',[' + STUFF(@ParamName,1,1,'') + '] ' + + CASE + WHEN @IsTableType = 1 + THEN 'XML' + WHEN @TypeName LIKE '%nchar%' + OR @TypeName LIKE '%nvarchar%' + THEN 'NVARCHAR(MAX)' + WHEN @TypeName LIKE '%char%' + THEN 'VARCHAR(MAX)' + ELSE @TypeName + END + ' NULL'; + + SELECT @SeparatorWithoutCursor = ','; + END + ELSE + BEGIN + SELECT @ProcParmTypeList = @ProcParmTypeList + @SeparatorWithCursor + @ParamName + ' CURSOR VARYING OUTPUT'; + END; + SELECT + @ProcParmListForCall = @ProcParmListForCall + @SeparatorWithCursor + @ParamName + + CASE + WHEN @IsOutput = 1 AND @IsCursorRef <> 1 + THEN ' OUT' + ELSE '' + END; + SELECT @SeparatorWithCursor = ','; + + FETCH NEXT FROM Parameters INTO @ParamName, @TypeName, @IsOutput, @IsCursorRef, @IsTableType; + END; + + CLOSE Parameters; + DEALLOCATE Parameters; + + DECLARE @InsertStmt NVARCHAR(MAX); + SELECT @InsertStmt = 'INSERT INTO ' + @LogTableName + + CASE WHEN @TableColList = '' THEN ' DEFAULT VALUES' + ELSE ' (' + @TableColList + ') SELECT ' + @ProcParmListForInsert + END + ';'; + + SELECT @CreateLogTableStatement = 'CREATE TABLE ' + @LogTableName + ' (_id_ int IDENTITY(1,1) PRIMARY KEY CLUSTERED ' + @TableColTypeList + ');'; + + SELECT @CreateProcedureStatement = + 'CREATE PROCEDURE ' + @OriginalProcedureName + ' ' + @ProcParmTypeList + + ' AS BEGIN ' + + ISNULL(@InsertStmt,'') + + ISNULL('DECLARE @SpyProcedureOriginalObjectName NVARCHAR(MAX) = '''+REPLACE(QUOTENAME(OBJECT_SCHEMA_NAME(@ProcedureObjectId))+'.'+QUOTENAME(@UnquotedNewNameOfProcedure),'''','''''')+''';','')+ + ISNULL(@CommandToExecute + ';', '') + + CHAR(13)+CHAR(10)+/*CR,LF*/ + CASE WHEN @CallOriginal = 1 + THEN 'EXEC @SpyProcedureOriginalObjectName ' + @ProcParmListForCall + ';' + ELSE '' + END + + ' RETURN;' + + ' END;'; + --RAISERROR(@CreateProcedureStatement, 0, 1) WITH NOWAIT; + + RETURN; +END; +---Build- +GO diff --git a/Source/tSQLt.Private_GetAssemblyKeyBytes.mdl.sql b/Source/tSQLt.Private_GetAssemblyKeyBytes.mdl.sql new file mode 100644 index 000000000..7e7d3074b --- /dev/null +++ b/Source/tSQLt.Private_GetAssemblyKeyBytes.mdl.sql @@ -0,0 +1,15 @@ +IF OBJECT_ID('tSQLt.Private_GetAssemblyKeyBytes') IS NOT NULL DROP PROCEDURE tSQLt.Private_GetAssemblyKeyBytes; +GO + +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_GetAssemblyKeyBytes + @AssemblyKeyBytes VARBINARY(MAX) = NULL OUTPUT, + @AssemblyKeyThumbPrint VARBINARY(MAX) = NULL OUTPUT +AS + SELECT @AssemblyKeyBytes = +0x000000 + ,@AssemblyKeyThumbPrint = 0x000001 ; +GO +---Build- +GO diff --git a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql new file mode 100644 index 000000000..bd6d6ef8d --- /dev/null +++ b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql @@ -0,0 +1,86 @@ +IF OBJECT_ID('tSQLt.Private_GetDropItemCmd') IS NOT NULL DROP FUNCTION tSQLt.Private_GetDropItemCmd; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_GetDropItemCmd +( +/*SnipParamStart: CreateDropClassStatement.ps1*/ + @FullName NVARCHAR(MAX), + @ItemType NVARCHAR(MAX) +/*SnipParamEnd: CreateDropClassStatement.ps1*/ +) +RETURNS TABLE +AS +RETURN +/*SnipStart: CreateDropClassStatement.ps1*/ +SELECT + CASE @ItemType + WHEN 'F' THEN 'ALTER TABLE '+(SELECT QUOTENAME(SCHEMA_NAME(schema_id))+'.'+QUOTENAME(OBJECT_NAME(parent_object_id)) FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(@FullName))+' ' + ELSE '' + END+ + 'DROP ' + + CASE @ItemType + WHEN 'F' THEN 'CONSTRAINT' + WHEN 'IF' THEN 'FUNCTION' + WHEN 'TF' THEN 'FUNCTION' + WHEN 'FN' THEN 'FUNCTION' + WHEN 'FT' THEN 'FUNCTION' + WHEN 'P' THEN 'PROCEDURE' + WHEN 'PC' THEN 'PROCEDURE' + WHEN 'SN' THEN 'SYNONYM' + WHEN 'U' THEN 'TABLE' + WHEN 'V' THEN 'VIEW' + WHEN 'type' THEN 'TYPE' + WHEN 'xml_schema_collection' THEN 'XML SCHEMA COLLECTION' + WHEN 'schema' THEN 'SCHEMA' + END+ + ' ' + + CASE @ItemType + WHEN 'F' THEN QUOTENAME(OBJECT_NAME(OBJECT_ID(@FullName))) + ELSE @FullName + END+ + ';' AS cmd +/*SnipEnd: CreateDropClassStatement.ps1*/ +GO +---Build- +/* +Object type: + AF = Aggregate function (CLR) +- C = CHECK constraint +- D = DEFAULT (constraint or stand-alone) ++ F = FOREIGN KEY constraint ++ FN = SQL scalar function + FS = Assembly (CLR) scalar-function ++ FT = Assembly (CLR) table-valued function ++ IF = SQL inline table-valued function + IT = Internal table ++ P = SQL Stored Procedure ++ PC = Assembly (CLR) stored-procedure +- PG = Plan guide +- PK = PRIMARY KEY constraint +? R = Rule (old-style, stand-alone) + RF = Replication-filter-procedure +- S = System base table + SN = Synonym + SO = Sequence object ++ U = Table (user-defined) ++ V = View +- EC = Edge constraint + +Applies to: SQL Server 2012 (11.x) and later. + SQ = Service queue + - TA = Assembly (CLR) DML trigger + + TF = SQL table-valued-function + - TR = SQL DML trigger + TT = Table type + - UQ = UNIQUE constraint + ? X = Extended stored procedure + +Applies to: SQL Server 2014 (12.x) and later, Azure SQL Database, Azure Synapse Analytics, Analytics Platform System (PDW). + ? ST = STATS_TREE + +Applies to: SQL Server 2016 (13.x) and later, Azure SQL Database, Azure Synapse Analytics, Analytics Platform System (PDW). + ET = External Table + +Also think about schema bound objects (an exercise in sorting?? because they need to be dropped in the correct order so that you don't drop parent objects before the child objects) +*/ diff --git a/Source/tSQLt.Private_GetExternalAccessKeyBytes.mdl.sql b/Source/tSQLt.Private_GetExternalAccessKeyBytes.mdl.sql deleted file mode 100644 index 22406d33d..000000000 --- a/Source/tSQLt.Private_GetExternalAccessKeyBytes.mdl.sql +++ /dev/null @@ -1,8 +0,0 @@ ----Build+ -GO -CREATE FUNCTION tSQLt.Private_GetExternalAccessKeyBytes() -RETURNS TABLE -AS -RETURN - SELECT 0x000000 AS ExternalAccessKeyBytes, 0x000001 AS ExternalAccessKeyThumbPrint; -GO diff --git a/Source/tSQLt.Private_GetForeignKeyDefinition.sfn.sql b/Source/tSQLt.Private_GetForeignKeyDefinition.sfn.sql index a801c465d..925bf6fae 100644 --- a/Source/tSQLt.Private_GetForeignKeyDefinition.sfn.sql +++ b/Source/tSQLt.Private_GetForeignKeyDefinition.sfn.sql @@ -79,7 +79,7 @@ RETURN SELECT 'CONSTRAINT ' + name + ' FOREIGN KEY (' + CROSS APPLY tSQLt.Private_GetForeignKeyParColumns(k.object_id) AS parCol CROSS APPLY tSQLt.Private_GetForeignKeyRefColumns(k.object_id) AS refCol LEFT JOIN sys.extended_properties e - ON e.name = 'tSQLt.FakeTable_OrgTableName' + ON e.name = 'tSQLt.Private_TestDouble_OrgObjectName' AND e.value = OBJECT_NAME(k.referenced_object_id) JOIN sys.tables refTab ON COALESCE(e.major_id,k.referenced_object_id) = refTab.object_id diff --git a/Source/tSQLt.Private_GetFormattedErrorInfo.sfn.sql b/Source/tSQLt.Private_GetFormattedErrorInfo.sfn.sql new file mode 100644 index 000000000..bb38fd081 --- /dev/null +++ b/Source/tSQLt.Private_GetFormattedErrorInfo.sfn.sql @@ -0,0 +1,17 @@ +IF OBJECT_ID('tSQLt.Private_GetFormattedErrorInfo') IS NOT NULL DROP FUNCTION tSQLt.Private_GetFormattedErrorInfo; +GO +---Build+ +CREATE FUNCTION tSQLt.Private_GetFormattedErrorInfo() +RETURNS TABLE +AS +RETURN + SELECT 'Message: ' + ISNULL(ERROR_MESSAGE(),'') + ' | Procedure: ' + ISNULL(ERROR_PROCEDURE(),'') + ISNULL(' (' + CAST(ERROR_LINE() AS NVARCHAR(MAX)) + ')','') + ' | Severity, State: ' + ISNULL(CAST(ERROR_SEVERITY() AS NVARCHAR(MAX)),'') + ', ' + ISNULL(CAST(ERROR_STATE() AS NVARCHAR(MAX)), '') + ' | Number: ' + ISNULL(CAST(ERROR_NUMBER() AS NVARCHAR(MAX)), '') AS FormattedError; +GO +---Build- +GO +BEGIN TRY + SELECT CAST('A' AS INT); +END TRY +BEGIN CATCH + SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo(); +END CATCH \ No newline at end of file diff --git a/Source/tSQLt.Private_GetFullTypeName.sfn.sql b/Source/tSQLt.Private_GetFullTypeName.sfn.sql index 045c2245a..4ff4b1bba 100644 --- a/Source/tSQLt.Private_GetFullTypeName.sfn.sql +++ b/Source/tSQLt.Private_GetFullTypeName.sfn.sql @@ -17,6 +17,8 @@ FROM( THEN '(' + CAST(@Length AS NVARCHAR) + ')' WHEN T.name IN ('decimal', 'numeric') THEN '(' + CAST(@Precision AS NVARCHAR) + ',' + CAST(@Scale AS NVARCHAR) + ')' + WHEN T.name IN ('datetime2', 'datetimeoffset', 'time') + THEN '(' + CAST(@Scale AS NVARCHAR) + ')' ELSE '' END Suffix, CASE WHEN @CollationName IS NULL OR T.is_user_defined = 1 THEN '' diff --git a/Source/tSQLt.Private_GetSQLProductMajorVersion.ssp.sql b/Source/tSQLt.Private_GetSQLProductMajorVersion.ssp.sql new file mode 100644 index 000000000..d508a9de0 --- /dev/null +++ b/Source/tSQLt.Private_GetSQLProductMajorVersion.ssp.sql @@ -0,0 +1,14 @@ +IF OBJECT_ID('tSQLt.Private_GetSQLProductMajorVersion') IS NOT NULL DROP PROCEDURE tSQLt.Private_GetSQLProductMajorVersion; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_GetSQLProductMajorVersion +AS + RETURN CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(MAX)),4) AS INT); +GO +---Build- +GO +DECLARE @Version INT; +EXEC @Version = tSQLt.Private_GetSQLProductMajorVersion; +PRINT 'SQL Major Version: '+CAST(@Version as NVARCHAR(MAX)); + diff --git a/Source/tSQLt.Private_HandleMessageAndResult.sfn.sql b/Source/tSQLt.Private_HandleMessageAndResult.sfn.sql new file mode 100644 index 000000000..2379fa0e5 --- /dev/null +++ b/Source/tSQLt.Private_HandleMessageAndResult.sfn.sql @@ -0,0 +1,22 @@ +IF OBJECT_ID('tSQLt.Private_HandleMessageAndResult') IS NOT NULL DROP FUNCTION tSQLt.Private_HandleMessageAndResult; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_HandleMessageAndResult ( + @PrevMessage NVARCHAR(MAX), + @PrevResult NVARCHAR(MAX), + @NewMessage NVARCHAR(MAX), + @NewResult NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT CASE WHEN ISNULL(@PrevMessage,'') NOT LIKE '%[^ '+CHAR(9)+']%' AND @PrevResult = 'Success' THEN '' + ELSE CASE WHEN @PrevMessage NOT LIKE '%[^ '+CHAR(9)+']%' THEN '' ELSE ISNULL(@PrevMessage,'') END+' [Result: '+ + CASE WHEN @PrevResult NOT LIKE '%[^ '+CHAR(9)+']%' THEN '' ELSE ISNULL(@PrevResult,'') END+'] || ' + END+ + CASE WHEN @NewMessage NOT LIKE '%[^ '+CHAR(9)+']%' THEN '' ELSE ISNULL(@NewMessage,'') END Message, + (SELECT TOP(1) Result FROM tSQLt.Private_Results WHERE Result IN (@PrevResult, @NewResult) ORDER BY Severity DESC) Result; +GO +---Build- +GO diff --git a/Source/tSQLt.Private_HostPlatform.stub.sql b/Source/tSQLt.Private_HostPlatform.stub.sql new file mode 100644 index 000000000..62b175054 --- /dev/null +++ b/Source/tSQLt.Private_HostPlatform.stub.sql @@ -0,0 +1,6 @@ +IF OBJECT_ID('tSQLt.Private_HostPlatform') IS NOT NULL DROP VIEW tSQLt.Private_HostPlatform; +GO +---Build+ +CREATE VIEW tSQLt.Private_HostPlatform AS SELECT CAST('Windows' AS NVARCHAR(256)) AS host_platform; +---Build- +GO diff --git a/Source/tSQLt.Private_HostPlatform.svw.sql b/Source/tSQLt.Private_HostPlatform.svw.sql new file mode 100644 index 000000000..d2d3089fa --- /dev/null +++ b/Source/tSQLt.Private_HostPlatform.svw.sql @@ -0,0 +1,8 @@ +GO +---Build+ +IF((SELECT SqlVersion FROM tSQLt.Info())>=14) +BEGIN + EXEC('CREATE OR ALTER VIEW tSQLt.Private_HostPlatform AS SELECT host_platform FROM sys.dm_os_host_info;'); +END; +---Build- +GO diff --git a/Source/tSQLt.Private_Init.ssp.sql b/Source/tSQLt.Private_Init.ssp.sql index 2c247541f..4190cc741 100644 --- a/Source/tSQLt.Private_Init.ssp.sql +++ b/Source/tSQLt.Private_Init.ssp.sql @@ -16,13 +16,14 @@ BEGIN RAISERROR('Cannot access CLR. Assembly might be in an invalid state. Try running EXEC tSQLt.EnableExternalAccess @enable = 0; or reinstalling tSQLt.',16,10); RETURN; END CATCH; - IF(@version_match = 0) + SELECT @version_match = CASE WHEN I.SqlVersion = I.InstalledOnSqlVersion THEN 1 ELSE 0 END FROM tSQLt.Info() AS I WHERE @version_match = 1; + IF(@version_match = 0 OR EXISTS(SELECT 1 FROM tSQLt.Private_Seize)) BEGIN RAISERROR('tSQLt is in an invalid state. Please reinstall tSQLt.',16,10); RETURN; END; - IF((SELECT SqlEdition FROM tSQLt.Info()) <> 'SQL Azure') + IF(NOT EXISTS(SELECT 1 FROM tSQLt.Info() WHERE SqlEdition = 'SQL Azure')) BEGIN EXEC tSQLt.EnableExternalAccess @enable = @enable, @try = 1; END; diff --git a/Source/tSQLt.Private_InstallationInfo.sfn.sql b/Source/tSQLt.Private_InstallationInfo.sfn.sql new file mode 100644 index 000000000..b4fcdc03b --- /dev/null +++ b/Source/tSQLt.Private_InstallationInfo.sfn.sql @@ -0,0 +1,22 @@ +IF OBJECT_ID('tSQLt.Private_CreateInstallationInfo') IS NOT NULL DROP PROCEDURE tSQLt.Private_CreateInstallationInfo; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_CreateInstallationInfo +-- Created as a stored procedure to make it testable. +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + SELECT + @cmd = 'ALTER FUNCTION tSQLt.Private_InstallationInfo() RETURNS TABLE AS RETURN SELECT CAST('+ + CAST(I.SqlVersion AS NVARCHAR(MAX))+ + ' AS NUMERIC(10,2)) AS SqlVersion;' + FROM tSQLt.Info() AS I; + + EXEC(@cmd); +END; +GO +EXEC tSQLt.Private_CreateInstallationInfo; +GO +---Build- +GO diff --git a/Source/tSQLt.Private_InstallationInfo.stub.sql b/Source/tSQLt.Private_InstallationInfo.stub.sql new file mode 100644 index 000000000..0f1e73e94 --- /dev/null +++ b/Source/tSQLt.Private_InstallationInfo.stub.sql @@ -0,0 +1,11 @@ +IF OBJECT_ID('tSQLt.Private_InstallationInfo') IS NOT NULL DROP FUNCTION tSQLt.Private_InstallationInfo; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_InstallationInfo() +RETURNS TABLE +AS +RETURN SELECT CAST(NULL AS NUMERIC(10,2)) AS SqlVersion; +GO +---Build- +GO \ No newline at end of file diff --git a/Source/tSQLt.Private_ListTestAnnotations.sfn.sql b/Source/tSQLt.Private_ListTestAnnotations.sfn.sql new file mode 100644 index 000000000..95b5bdb81 --- /dev/null +++ b/Source/tSQLt.Private_ListTestAnnotations.sfn.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.Private_ListTestAnnotations') IS NOT NULL DROP FUNCTION tSQLt.Private_ListTestAnnotations; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_ListTestAnnotations( + @TestObjectId INT +) +RETURNS TABLE +AS +RETURN + SELECT + GAL.AnnotationNo, + REPLACE(GAL.Annotation,'''','''''') AS EscapedAnnotationString, + 'tSQLt.'+GAL.Annotation AS Annotation + FROM tSQLt.Private_GetAnnotationList(OBJECT_DEFINITION(@TestObjectId))AS GAL; +GO +---Build- +GO diff --git a/Source/tSQLt.Private_Lock.tbl.sql b/Source/tSQLt.Private_Lock.tbl.sql new file mode 100644 index 000000000..855337854 --- /dev/null +++ b/Source/tSQLt.Private_Lock.tbl.sql @@ -0,0 +1,7 @@ +IF OBJECT_ID('tSQLt.Private_Lock') IS NOT NULL DROP TABLE tSQLt.Private_Lock; +GO +--DECLARE @Lock INT = (SELECT COUNT(1) FROM tSQLt.Lock AS L WITH(TABLOCKX)); +---Build+ +GO +CREATE TABLE tSQLt.Private_Lock(X BIT CONSTRAINT [PK:tSQLt.Private_Lock] PRIMARY KEY CONSTRAINT [CHK:tSQLt.Private_Lock(X)] CHECK(X=13)); +GO diff --git a/Source/tSQLt.Private_MarkFakeTable.ssp.sql b/Source/tSQLt.Private_MarkFakeTable.ssp.sql deleted file mode 100644 index df07eefb4..000000000 --- a/Source/tSQLt.Private_MarkFakeTable.ssp.sql +++ /dev/null @@ -1,20 +0,0 @@ -IF OBJECT_ID('tSQLt.Private_MarkFakeTable') IS NOT NULL DROP PROCEDURE tSQLt.Private_MarkFakeTable; -GO ----Build+ -CREATE PROCEDURE tSQLt.Private_MarkFakeTable - @SchemaName NVARCHAR(MAX), - @TableName NVARCHAR(MAX), - @NewNameOfOriginalTable NVARCHAR(4000) -AS -BEGIN - DECLARE @UnquotedSchemaName NVARCHAR(MAX);SET @UnquotedSchemaName = OBJECT_SCHEMA_NAME(OBJECT_ID(@SchemaName+'.'+@TableName)); - DECLARE @UnquotedTableName NVARCHAR(MAX);SET @UnquotedTableName = OBJECT_NAME(OBJECT_ID(@SchemaName+'.'+@TableName)); - - EXEC sys.sp_addextendedproperty - @name = N'tSQLt.FakeTable_OrgTableName', - @value = @NewNameOfOriginalTable, - @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, - @level1type = N'TABLE', @level1name = @UnquotedTableName; -END; ----Build- -GO \ No newline at end of file diff --git a/Source/tSQLt.Private_MarktSQLtTempObject.ssp.sql b/Source/tSQLt.Private_MarktSQLtTempObject.ssp.sql new file mode 100644 index 000000000..fa12ef231 --- /dev/null +++ b/Source/tSQLt.Private_MarktSQLtTempObject.ssp.sql @@ -0,0 +1,59 @@ +IF OBJECT_ID('tSQLt.Private_MarktSQLtTempObject') IS NOT NULL DROP PROCEDURE tSQLt.Private_MarktSQLtTempObject; +GO +---Build+ +CREATE PROCEDURE tSQLt.Private_MarktSQLtTempObject + @ObjectName NVARCHAR(MAX), + @ObjectType NVARCHAR(MAX), + @NewNameOfOriginalObject NVARCHAR(4000) = NULL +AS +BEGIN + DECLARE @UnquotedSchemaName NVARCHAR(MAX); + DECLARE @UnquotedObjectName NVARCHAR(MAX); + DECLARE @UnquotedParentName NVARCHAR(MAX); + DECLARE @TempObjectFlagOn BIT = 1; + SELECT + @UnquotedSchemaName = SCHEMA_NAME(O.schema_id), + @UnquotedObjectName = O.name, + @UnquotedParentName = OBJECT_NAME(O.parent_object_id) + FROM sys.objects O + WHERE O.object_id = OBJECT_ID(@ObjectName); + + IF(@UnquotedParentName IS NULL) + BEGIN + EXEC sys.sp_addextendedproperty + @name = N'tSQLt.IsTempObject', + @value = @TempObjectFlagOn, + @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, + @level1type = @ObjectType, @level1name = @UnquotedObjectName; + + IF(@NewNameOfOriginalObject IS NOT NULL) + BEGIN + EXEC sys.sp_addextendedproperty + @name = N'tSQLt.Private_TestDouble_OrgObjectName', + @value = @NewNameOfOriginalObject, + @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, + @level1type = @ObjectType, @level1name = @UnquotedObjectName; + END; + END; + ELSE + BEGIN + EXEC sys.sp_addextendedproperty + @name = N'tSQLt.IsTempObject', + @value = @TempObjectFlagOn, + @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, + @level1type = N'TABLE', @level1name = @UnquotedParentName, + @level2type = @ObjectType, @level2name = @UnquotedObjectName; + + IF(@NewNameOfOriginalObject IS NOT NULL) + BEGIN + EXEC sys.sp_addextendedproperty + @name = N'tSQLt.Private_TestDouble_OrgObjectName', + @value = @NewNameOfOriginalObject, + @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, + @level1type = N'TABLE', @level1name = @UnquotedParentName, + @level2type = @ObjectType, @level2name = @UnquotedObjectName; + END; + END; +END; +---Build- +GO \ No newline at end of file diff --git a/Source/tSQLt.Private_NoTransactionHandleTable.ssp.sql b/Source/tSQLt.Private_NoTransactionHandleTable.ssp.sql new file mode 100644 index 000000000..f7e580e1d --- /dev/null +++ b/Source/tSQLt.Private_NoTransactionHandleTable.ssp.sql @@ -0,0 +1,104 @@ +IF OBJECT_ID('tSQLt.Private_NoTransactionHandleTable') IS NOT NULL DROP PROCEDURE tSQLt.Private_NoTransactionHandleTable; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_NoTransactionHandleTable +@Action NVARCHAR(MAX), +@FullTableName NVARCHAR(MAX), +@TableAction NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + BEGIN TRY + IF (OBJECT_ID(@FullTableName) IS NULL AND @TableAction <> 'Hide') + BEGIN + RAISERROR('Table %s does not exist.',16,10,@FullTableName); + END; + IF (@Action = 'Save') + BEGIN + IF (@TableAction = 'Restore') + BEGIN + IF(NOT EXISTS(SELECT 1 FROM #TableBackupLog TBL WHERE TBL.OriginalName = @FullTableName)) + BEGIN + DECLARE @NewQuotedNameForBackupTable NVARCHAR(MAX) = '[tSQLt].'+QUOTENAME(tSQLt.Private::CreateUniqueObjectName()); + SET @cmd = 'SELECT * INTO '+@NewQuotedNameForBackupTable+' FROM '+@FullTableName+';'; + EXEC (@cmd); + INSERT INTO #TableBackupLog (OriginalName, BackupName) VALUES (@FullTableName, @NewQuotedNameForBackupTable); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = @NewQuotedNameForBackupTable, @ObjectType = N'TABLE', @NewNameOfOriginalObject = NULL; + END; + END; + ELSE IF (@TableAction = 'Hide') + BEGIN + IF (NOT EXISTS (SELECT 1 FROM tSQLt.Private_RenamedObjectLog ROL WHERE QUOTENAME(OBJECT_SCHEMA_NAME(ROL.ObjectId))+'.'+OriginalName = @FullTableName)) + BEGIN + IF(OBJECT_ID(@FullTableName) IS NULL) + BEGIN + RAISERROR('Table %s does not exist.',16,10,@FullTableName); + END; + EXEC tSQLt.RemoveObject @ObjectName = @FullTableName; + END; + END; + ELSE IF (@TableAction IN ('Truncate', 'Ignore')) + BEGIN + RETURN; + END; + ELSE + BEGIN + RAISERROR('Invalid @TableAction parameter value.',16,10); + END; + END; + ELSE IF (@Action = 'Reset') + BEGIN + IF (@TableAction = 'Restore') + BEGIN + BEGIN TRAN; + DECLARE @BackupTableName TABLE(TableName NVARCHAR(MAX)); + DELETE FROM #TableBackupLog OUTPUT DELETED.BackupName INTO @BackupTableName WHERE OriginalName = @FullTableName; + IF(EXISTS(SELECT 1 FROM @BackupTableName AS BTN)) + BEGIN + SET @cmd = 'DELETE FROM ' + @FullTableName + ';'; + IF (EXISTS(SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID(@FullTableName) AND is_identity = 1)) + BEGIN + SET @cmd = @cmd + 'SET IDENTITY_INSERT ' + @FullTableName + ' ON;'; + END; + SET @cmd = @cmd + 'INSERT INTO ' + @FullTableName +'('; + DECLARE @ColumnList NVARCHAR(MAX) = STUFF((SELECT ','+QUOTENAME(name) FROM sys.columns WHERE object_id = OBJECT_ID(@FullTableName) AND is_computed = 0 ORDER BY column_id FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,''); + SET @cmd = @cmd + @ColumnList; + SET @cmd = @cmd + ') SELECT ' + @ColumnList + ' FROM ' + (SELECT TableName FROM @BackupTableName)+';'; + EXEC(@cmd); + END; + COMMIT; + END; + ELSE IF (@TableAction = 'Truncate') + BEGIN + EXEC('DELETE FROM ' + @FullTableName +';'); + END; + ELSE IF (@TableAction IN ('Ignore','Hide')) + BEGIN + /* Hidden tables will be restored by UndoTestDoubles. */ + RETURN; + END; + ELSE + BEGIN + RAISERROR('Invalid @TableAction parameter value.', 16, 10); + END; + END; + ELSE + BEGIN + RAISERROR('Invalid @Action parameter value.',16,10); + END; + END TRY + BEGIN CATCH + DECLARE @ErrorLine INT = ERROR_LINE(); + DECLARE @ErrorProcedure NVARCHAR(MAX) = ERROR_PROCEDURE(); + DECLARE @ErrorMessage NVARCHAR(MAX) = ERROR_MESSAGE(); + DECLARE @ErrorSeverity INT = ERROR_SEVERITY(); + DECLARE @ErrorState INT = ERROR_STATE(); + RAISERROR('tSQLt is in an unknown state: Stopping execution. (%s | Procedure: %s | Line: %i)', @ErrorSeverity, @ErrorState, @ErrorMessage, @ErrorProcedure, @ErrorLine); + END CATCH; +END; +GO +--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX-- +--DECLARE @TempMsg58 NVARCHAR(MAX) = FORMATMESSAGE('HandleTable(58) - @BackupTableName = %s, @FullTableName = %s, XACT_STATE = %i, SummaryError = %i',(SELECT TableName FROM @BackupTableName), @FullTableName, XACT_STATE(), CAST((SELECT PGC.Value FROM tSQLt.Private_GetConfiguration('SummaryError') AS PGC) AS INT));RAISERROR(@TempMsg58, 0,1) WITH NOWAIT; +--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX-- + diff --git a/Source/tSQLt.Private_NoTransactionHandleTables.ssp.sql b/Source/tSQLt.Private_NoTransactionHandleTables.ssp.sql new file mode 100644 index 000000000..87a8af3c9 --- /dev/null +++ b/Source/tSQLt.Private_NoTransactionHandleTables.ssp.sql @@ -0,0 +1,17 @@ +IF OBJECT_ID('tSQLt.Private_NoTransactionHandleTables') IS NOT NULL DROP PROCEDURE tSQLt.Private_NoTransactionHandleTables; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_NoTransactionHandleTables + @Action NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX) = ( + SELECT 'EXEC tSQLt.Private_NoTransactionHandleTable @Action = '''+@Action+''', @FullTableName = '''+X.Name+''', @TableAction = '''+X.Action+''';' + FROM tSQLt.Private_NoTransactionTableAction X + WHERE X.Action <> 'Ignore' + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); +END; +GO \ No newline at end of file diff --git a/Source/tSQLt.Private_NoTransactionTableAction.view.sql b/Source/tSQLt.Private_NoTransactionTableAction.view.sql new file mode 100644 index 000000000..3b80a0673 --- /dev/null +++ b/Source/tSQLt.Private_NoTransactionTableAction.view.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.Private_NoTransactionTableAction') IS NOT NULL DROP VIEW tSQLt.Private_NoTransactionTableAction; +GO +---Build+ +GO +CREATE VIEW tSQLt.Private_NoTransactionTableAction +AS +SELECT CAST(Name AS NVARCHAR(MAX)) Name, CAST(Action AS NVARCHAR(MAX)) Action + FROM( + VALUES('[tSQLt].[Private_NewTestClassList]','Hide'), + ('[tSQLt].[Run_LastExecution]','Hide'), + ('[tSQLt].[Private_Configurations]','Restore'), + ('[tSQLt].[CaptureOutputLog]','Truncate'), + ('[tSQLt].[Private_RenamedObjectLog]','Ignore'), + ('[tSQLt].[Private_Seize]','Ignore'), + ('[tSQLt].[Private_Seize_NoTruncate]','Ignore'), + ('[tSQLt].[TestResult]','Restore') + )X(Name, Action); +GO diff --git a/Source/tSQLt.Private_NullCellTable.tbl.sql b/Source/tSQLt.Private_NullCellTable.tbl.sql deleted file mode 100644 index c47c63b06..000000000 --- a/Source/tSQLt.Private_NullCellTable.tbl.sql +++ /dev/null @@ -1,18 +0,0 @@ -IF OBJECT_ID('tSQLt.Private_NullCellTable') IS NOT NULL DROP TABLE tSQLt.Private_NullCellTable; -GO ----Build+ -GO -CREATE TABLE tSQLt.Private_NullCellTable( - I INT -); -GO - -INSERT INTO tSQLt.Private_NullCellTable (I) VALUES (NULL); -GO - -CREATE TRIGGER tSQLt.Private_NullCellTable_StopDeletes ON tSQLt.Private_NullCellTable INSTEAD OF DELETE, INSERT, UPDATE -AS -BEGIN - RETURN; -END; -GO diff --git a/Source/tSQLt.Private_PrepareFakeFunctionOutputTable.ssp.sql b/Source/tSQLt.Private_PrepareFakeFunctionOutputTable.ssp.sql new file mode 100644 index 000000000..6f64bea55 --- /dev/null +++ b/Source/tSQLt.Private_PrepareFakeFunctionOutputTable.ssp.sql @@ -0,0 +1,26 @@ +IF OBJECT_ID('tSQLt.Private_PrepareFakeFunctionOutputTable') IS NOT NULL + DROP PROCEDURE tSQLt.Private_PrepareFakeFunctionOutputTable; +GO +---Build+ +CREATE PROCEDURE tSQLt.Private_PrepareFakeFunctionOutputTable + @FakeDataSource NVARCHAR(MAX), + @OutputTable NVARCHAR(MAX) OUTPUT +AS + BEGIN + SET @OutputTable = tSQLt.Private::CreateUniqueObjectName(); + + IF ( LOWER(LTRIM(@FakeDataSource)) LIKE 'select%' + AND OBJECT_ID(@FakeDataSource) IS NULL + ) + BEGIN + SET @FakeDataSource = N'('+ @FakeDataSource + N') a'; + END; + + DECLARE @Cmd NVARCHAR(MAX) = N'SELECT * INTO ' + @OutputTable + N' FROM ' + @FakeDataSource; + + EXEC sp_executesql @Cmd; + + RETURN 0; + END; +---Build- +GO diff --git a/Source/tSQLt.Private_ProcessTestAnnotations.ssp.sql b/Source/tSQLt.Private_ProcessTestAnnotations.ssp.sql new file mode 100644 index 000000000..26a52782e --- /dev/null +++ b/Source/tSQLt.Private_ProcessTestAnnotations.ssp.sql @@ -0,0 +1,105 @@ +IF OBJECT_ID('tSQLt.Private_ProcessTestAnnotations') IS NOT NULL DROP PROCEDURE tSQLt.Private_ProcessTestAnnotations; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_ProcessTestAnnotations + @TestObjectId INT +AS +BEGIN + DECLARE @Cmd NVARCHAR(MAX); + DECLARE @UnmatchedQuotesAnnotation NVARCHAR(MAX); + CREATE TABLE #AnnotationCommands(AnnotationOrderNo INT, AnnotationString NVARCHAR(MAX), AnnotationCmd NVARCHAR(MAX)); + + SELECT * INTO #AnnotationList FROM tSQLt.Private_ListTestAnnotations(@TestObjectId); + SET @UnmatchedQuotesAnnotation = NULL; + SELECT TOP(1) @UnmatchedQuotesAnnotation = AL.Annotation + FROM #AnnotationList AS AL + WHERE (LEN(AL.Annotation ) - LEN(REPLACE(AL.Annotation, '''', '')))%2=1 + ORDER BY AL.AnnotationNo; + + IF(@UnmatchedQuotesAnnotation IS NOT NULL) + BEGIN + RAISERROR('Annotation has unmatched quote: %s',16,10,@UnmatchedQuotesAnnotation); + END; + + SELECT @Cmd = + 'DECLARE @EM NVARCHAR(MAX),@ES INT,@ET INT,@EP NVARCHAR(MAX);'+ + ( + SELECT + 'BEGIN TRY;INSERT INTO #AnnotationCommands '+ + 'SELECT '+ + CAST(AL.AnnotationNo AS NVARCHAR(MAX))+','+ + ''''+AL.EscapedAnnotationString+''''+ + ',A.AnnotationCmd FROM '+ + AL.Annotation+' AS A;'+ + ';END TRY BEGIN CATCH;'+ + 'SELECT @EM=ERROR_MESSAGE(),'+--REPLACE(ERROR_MESSAGE(),'''''''',''''''''''''),'+ + '@ES=ERROR_SEVERITY(),'+ + '@ET=ERROR_STATE();'+ + 'RAISERROR(''There is an internal error for annotation: %s'+CHAR(13)+CHAR(10)+ + ' caused by {%i,%i} %s'',16,10,'''+ + AL.EscapedAnnotationString+ + ''',@ES,@ET,@EM);'+ + 'END CATCH;' + FROM #AnnotationList AS AL + ORDER BY AL.AnnotationNo + FOR XML PATH,TYPE + ).value('.','NVARCHAR(MAX)'); + + IF(@Cmd IS NOT NULL) + BEGIN + --PRINT '--------------------------------'; + --PRINT @Cmd + --PRINT '--------------------------------'; + BEGIN TRY + EXEC(@Cmd); + END TRY + BEGIN CATCH + DECLARE @EM NVARCHAR(MAX),@ES INT,@ET INT,@EP NVARCHAR(MAX); + SELECT @EM=REPLACE(ERROR_MESSAGE(),'''',''''''), + @ES=ERROR_SEVERITY(), + @ET=ERROR_STATE(); + DECLARE @NewErrorMessage NVARCHAR(MAX)= + 'There is a problem with the annotations:'+CHAR(13)+CHAR(10)+ + 'Original Error: {%i,%i} %s' + RAISERROR(@NewErrorMessage,16,10,@ES,@ET,@EM); + END CATCH; + --PRINT '--------------------------------'; + + + SELECT @Cmd = + 'DECLARE @EM NVARCHAR(MAX),@ES INT,@ET INT,@EP NVARCHAR(MAX);'+ + ( + SELECT + 'BEGIN TRY;'+ + 'IF(NOT EXISTS(SELECT 1 FROM #SkipTest)) BEGIN '+ + AnnotationCmd+ + ';END'+ + ';END TRY BEGIN CATCH;'+ + 'SELECT @EM=ERROR_MESSAGE(),'+--REPLACE(ERROR_MESSAGE(),'''''''',''''''''''''),'+ + '@ES=ERROR_SEVERITY(),'+ + '@ET=ERROR_STATE(),'+ + '@EP=ERROR_PROCEDURE();'+ + 'RAISERROR(''There is a problem with this annotation: %s'+CHAR(13)+CHAR(10)+ + 'Original Error: {%i,%i;%s} %s'',16,10,'''+ + REPLACE(AnnotationString,'''','''''')+ + ''',@ES,@ET,@EP,@EM);'+ + 'END CATCH;' + FROM #AnnotationCommands + ORDER BY AnnotationOrderNo + FOR XML PATH,TYPE + ).value('.','NVARCHAR(MAX)'); + + IF(@Cmd IS NOT NULL) + BEGIN + --PRINT '--------------------------------'; + --PRINT @Cmd + --PRINT '--------------------------------'; + EXEC(@Cmd); + END; + + END; + +END; +GO +---Build- \ No newline at end of file diff --git a/Source/tSQLt.Private_RenameObject.ssp.sql b/Source/tSQLt.Private_RenameObject.ssp.sql new file mode 100644 index 000000000..7dd62db87 --- /dev/null +++ b/Source/tSQLt.Private_RenameObject.ssp.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.Private_RenameObject') IS NOT NULL DROP PROCEDURE tSQLt.Private_RenameObject; +GO +---Build+ +CREATE PROCEDURE tSQLt.Private_RenameObject + @SchemaName NVARCHAR(MAX), + @ObjectName NVARCHAR(MAX), + @NewName NVARCHAR(MAX) +AS +BEGIN + DECLARE @RenameCmd NVARCHAR(MAX); + SET @RenameCmd = 'EXEC sp_rename ''' + + REPLACE(@SchemaName + '.' + @ObjectName, '''', '''''') + ''', ''' + + REPLACE(@NewName, '''', '''''') + ''',''OBJECT'';'; + + EXEC tSQLt.SuppressOutput @RenameCmd; +END; +---Build- +GO diff --git a/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql b/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql index 8b87f57ab..e50bf7745 100644 --- a/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql +++ b/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql @@ -7,17 +7,13 @@ CREATE PROCEDURE tSQLt.Private_RenameObjectToUniqueName @NewName NVARCHAR(MAX) = NULL OUTPUT AS BEGIN - SET @NewName=tSQLt.Private::CreateUniqueObjectName(); - - DECLARE @RenameCmd NVARCHAR(MAX); - SET @RenameCmd = 'EXEC sp_rename ''' + - @SchemaName + '.' + @ObjectName + ''', ''' + - @NewName + ''',''OBJECT'';'; + SET @NewName=ISNULL(@NewName, tSQLt.Private::CreateUniqueObjectName()); EXEC tSQLt.Private_MarkObjectBeforeRename @SchemaName, @ObjectName; - - EXEC tSQLt.SuppressOutput @RenameCmd; + EXEC tSQLt.Private_RenameObject @SchemaName, + @ObjectName, + @NewName; END; ---Build- diff --git a/Source/tSQLt.Private_Results.view.sql b/Source/tSQLt.Private_Results.view.sql new file mode 100644 index 000000000..4a628284d --- /dev/null +++ b/Source/tSQLt.Private_Results.view.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.Private_Results') IS NOT NULL DROP VIEW tSQLt.Private_Results; +GO +---Build+ +GO +CREATE VIEW tSQLt.Private_Results +AS +SELECT CAST(Severity AS INT) Severity,CAST(Result AS NVARCHAR(MAX)) Result + FROM( + VALUES(1, 'Success') + , + (2, 'Skipped'), + (3, 'Failure'), + (4, 'Error'), + (5, 'Abort'), + (6, 'FATAL') + )X(Severity, Result); +GO + diff --git a/Source/tSQLt.Private_Seize.tbl.sql b/Source/tSQLt.Private_Seize.tbl.sql new file mode 100644 index 000000000..eb17bb3d7 --- /dev/null +++ b/Source/tSQLt.Private_Seize.tbl.sql @@ -0,0 +1,21 @@ +IF OBJECT_ID('tSQLt.Private_Seize_NoTruncate') IS NOT NULL DROP TABLE tSQLt.Private_Seize_NoTruncate; +IF OBJECT_ID('tSQLt.Private_Seize') IS NOT NULL DROP TABLE tSQLt.Private_Seize; +GO +---Build+ +GO +CREATE TABLE tSQLt.Private_Seize( + Kaput BIT CONSTRAINT [Private_Seize:PK] PRIMARY KEY CONSTRAINT [Private_Seize:CHK] CHECK(Kaput=1) +); +GO +CREATE TABLE tSQLt.Private_Seize_NoTruncate( + NoTruncate BIT CONSTRAINT [Private_Seize_NoTruncate(NoTruncate):FK] FOREIGN KEY REFERENCES tSQLt.Private_Seize(Kaput) +); +GO +CREATE TRIGGER tSQLt.Private_Seize_Stop ON tSQLt.Private_Seize INSTEAD OF DELETE,UPDATE +AS +BEGIN + RAISERROR('This is a private table that you should not mess with!',16,10); +END; +GO + + \ No newline at end of file diff --git a/Source/tSQLt.Private_SkipTestAnnotationHelper.ssp.sql b/Source/tSQLt.Private_SkipTestAnnotationHelper.ssp.sql new file mode 100644 index 000000000..63ca1ac1b --- /dev/null +++ b/Source/tSQLt.Private_SkipTestAnnotationHelper.ssp.sql @@ -0,0 +1,13 @@ +IF OBJECT_ID('tSQLt.Private_SkipTestAnnotationHelper') IS NOT NULL DROP PROCEDURE tSQLt.Private_SkipTestAnnotationHelper; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_SkipTestAnnotationHelper + @SkipReason NVARCHAR(MAX) +AS +BEGIN + INSERT INTO #SkipTest VALUES(@SkipReason); +END; +GO +---Build- +GO \ No newline at end of file diff --git a/Source/tSQLt.Private_SplitSqlVersion.sfn.sql b/Source/tSQLt.Private_SplitSqlVersion.sfn.sql new file mode 100644 index 000000000..b9b64168d --- /dev/null +++ b/Source/tSQLt.Private_SplitSqlVersion.sfn.sql @@ -0,0 +1,22 @@ +IF OBJECT_ID('tSQLt.Private_SplitSqlVersion') IS NOT NULL DROP FUNCTION tSQLt.Private_SplitSqlVersion; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_SplitSqlVersion(@ProductVersion NVARCHAR(128)) +/* Important: Do not rename the @ProducVersion parameter! */ +RETURNS TABLE +AS +RETURN +/* Important: Do not rename the @ProducVersion parameter! */ +/*StartSnip*/ +SELECT REVERSE(PARSENAME(X.RP,1)) Major, + REVERSE(PARSENAME(X.RP,2)) Minor, + REVERSE(PARSENAME(X.RP,3)) Build, + REVERSE(PARSENAME(X.RP,4)) Revision + FROM (SELECT REVERSE(@ProductVersion)) AS X(RP) +/*EndSnip*/ +; +/* Important: Do not rename the @ProducVersion parameter! */ +GO +---Build- +GO diff --git a/Source/tSQLt.Private_SqlVersion.sfn.sql b/Source/tSQLt.Private_SqlVersion.sfn.sql index 0c8e10ef5..bf3d5b426 100644 --- a/Source/tSQLt.Private_SqlVersion.sfn.sql +++ b/Source/tSQLt.Private_SqlVersion.sfn.sql @@ -6,8 +6,9 @@ CREATE FUNCTION tSQLt.Private_SqlVersion() RETURNS TABLE AS RETURN - SELECT CAST(SERVERPROPERTY('ProductVersion')AS NVARCHAR(128)) ProductVersion, - CAST(SERVERPROPERTY('Edition')AS NVARCHAR(128)) Edition; + SELECT + CAST(SERVERPROPERTY('ProductVersion')AS NVARCHAR(128)) ProductVersion, + CAST(SERVERPROPERTY('Edition')AS NVARCHAR(128)) Edition GO ---Build- GO diff --git a/Source/tSQLt.Private_SysTypes.svw.sql b/Source/tSQLt.Private_SysTypes.svw.sql index 7a818eb4b..8f318cff9 100644 --- a/Source/tSQLt.Private_SysTypes.svw.sql +++ b/Source/tSQLt.Private_SysTypes.svw.sql @@ -4,7 +4,7 @@ GO GO CREATE VIEW tSQLt.Private_SysTypes AS SELECT * FROM sys.types AS T; GO -IF(CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR(MAX)) LIKE '9.%') +IF((SELECT SqlVersion FROM tSQLt.Info())=9) BEGIN EXEC('ALTER VIEW tSQLt.Private_SysTypes AS SELECT *,0 is_table_type FROM sys.types AS T;'); END; diff --git a/Source/tSQLt.Private_TableToTextColumnListAdjustWidth.sfn.sql b/Source/tSQLt.Private_TableToTextColumnListAdjustWidth.sfn.sql new file mode 100644 index 000000000..40ddccd5d --- /dev/null +++ b/Source/tSQLt.Private_TableToTextColumnListAdjustWidth.sfn.sql @@ -0,0 +1,37 @@ +IF OBJECT_ID('tSQLt.Private_TableToTextColumnListAdjustWidth') IS NOT NULL DROP FUNCTION tSQLt.Private_TableToTextColumnListAdjustWidth; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_TableToTextColumnListAdjustWidth( + @tmpObjectId INT +) +RETURNS TABLE +AS +RETURN + SELECT + ( + SELECT + ',C'+NN+' = CASE WHEN LEN(C.C'+NN+')>L.MCW '+ + 'THEN LEFT(C.C'+NN+',(L.MCW-5)/2)+''<...>''+RIGHT(C.C'+NN+',(L.MCW-5)/2) '+ + 'ELSE LEFT(C.C'+NN+'+REPLICATE('' '','+ + 'CASE WHEN L.MCW < L.L'+NN+' '+ + 'THEN L.MCW ELSE L.L'+NN+' END - '+ + 'LEN(C.C'+NN+')),'+ + 'CASE WHEN L.MCW < L.L'+NN+' '+ + 'THEN L.MCW ELSE L.L'+NN+' END) '+ + 'END' + FROM( + SELECT + column_id,RIGHT(CAST(10002+ROW_NUMBER()OVER(ORDER BY column_id) AS NVARCHAR(MAX)),4) NN + FROM sys.columns + WHERE object_id = @tmpObjectId + AND column_id>1 + )X + ORDER BY column_id + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') [ColumnList]; + GO +---Build- +GO +--+REPLICATE(' ',LEN(C'+NN+')) +--',C'+NN+' = CASE WHEN LEN(C'+NN+')>L.MCW THEN LEFT(C'+NN+',(L.MCW-5)/2)+''<...>''+RIGHT(C'+NN+',(L.MCW-5)/2) ELSE LEFT(C'+NN+'+REPLICATE(' ',CASE WHEN L.MCW < L.L'+NN+' THEN L.MCW ELSE L.L'+NN+' END - LEN(C'+NN+')),9) END' \ No newline at end of file diff --git a/Source/tSQLt.Private_TableToTextColumnListMaxLenNumbered.sfn.sql b/Source/tSQLt.Private_TableToTextColumnListMaxLenNumbered.sfn.sql new file mode 100644 index 000000000..b99f6bcd1 --- /dev/null +++ b/Source/tSQLt.Private_TableToTextColumnListMaxLenNumbered.sfn.sql @@ -0,0 +1,26 @@ +IF OBJECT_ID('tSQLt.Private_TableToTextColumnListMaxLenNumbered') IS NOT NULL DROP FUNCTION tSQLt.Private_TableToTextColumnListMaxLenNumbered; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_TableToTextColumnListMaxLenNumbered( + @tmpObjectId INT +) +RETURNS TABLE +AS +RETURN + SELECT + ( + SELECT + ',MAX(LEN(''.''+C'+ + RIGHT(CAST(10002+ROW_NUMBER()OVER(ORDER BY column_id) AS NVARCHAR(MAX)),4)+ + '+''.''))-2 L'+ + RIGHT(CAST(10002+ROW_NUMBER()OVER(ORDER BY column_id) AS NVARCHAR(MAX)),4) + FROM sys.columns + WHERE object_id = @tmpObjectId + AND column_id>1 + ORDER BY column_id + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') [ColumnList]; + GO +---Build- +GO diff --git a/Source/tSQLt.Private_TableToTextColumnListQuotedAndNumbered.sfn.sql b/Source/tSQLt.Private_TableToTextColumnListQuotedAndNumbered.sfn.sql new file mode 100644 index 000000000..627dc3960 --- /dev/null +++ b/Source/tSQLt.Private_TableToTextColumnListQuotedAndNumbered.sfn.sql @@ -0,0 +1,26 @@ +IF OBJECT_ID('tSQLt.Private_TableToTextColumnListQuotedAndNumbered') IS NOT NULL DROP FUNCTION tSQLt.Private_TableToTextColumnListQuotedAndNumbered; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_TableToTextColumnListQuotedAndNumbered( + @tmpObjectId INT +) +RETURNS TABLE +AS +RETURN + SELECT + ( + SELECT + ',CAST('+ + QUOTENAME(name,'''')+ + ' AS NVARCHAR(MAX)) C'+ + RIGHT(CAST(10002+ROW_NUMBER()OVER(ORDER BY column_id) AS NVARCHAR(MAX)),4) + FROM sys.columns + WHERE object_id = @tmpObjectId + AND column_id>1 + ORDER BY column_id + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') [ColumnList]; + GO +---Build- +GO diff --git a/Source/tSQLt.Private_TableToTextColumnListSeparatorLine.sfn.sql b/Source/tSQLt.Private_TableToTextColumnListSeparatorLine.sfn.sql new file mode 100644 index 000000000..73da3b159 --- /dev/null +++ b/Source/tSQLt.Private_TableToTextColumnListSeparatorLine.sfn.sql @@ -0,0 +1,31 @@ +IF OBJECT_ID('tSQLt.Private_TableToTextColumnListSeparatorLine') IS NOT NULL DROP FUNCTION tSQLt.Private_TableToTextColumnListSeparatorLine; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_TableToTextColumnListSeparatorLine( + @tmpObjectId INT +) +RETURNS TABLE +AS +RETURN + SELECT + ( + SELECT + ',REPLICATE(''-'','+ + 'CASE WHEN L.MCW < L.L'+NN+' '+ + 'THEN L.MCW ELSE L.L'+NN+' END)' + FROM( + SELECT + column_id,RIGHT(CAST(10002+ROW_NUMBER()OVER(ORDER BY column_id) AS NVARCHAR(MAX)),4) NN + FROM sys.columns + WHERE object_id = @tmpObjectId + AND column_id>1 + )X + ORDER BY column_id + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') [ColumnList]; + GO +---Build- +GO +--+REPLICATE(' ',LEN(C'+NN+')) +--',C'+NN+' = CASE WHEN LEN(C'+NN+')>L.MCW THEN LEFT(C'+NN+',(L.MCW-5)/2)+''<...>''+RIGHT(C'+NN+',(L.MCW-5)/2) ELSE LEFT(C'+NN+'+REPLICATE(' ',CASE WHEN L.MCW < L.L'+NN+' THEN L.MCW ELSE L.L'+NN+' END - LEN(C'+NN+')),9) END' \ No newline at end of file diff --git a/Source/tSQLt.Private_TableToTextColumntoStringList.sfn.sql b/Source/tSQLt.Private_TableToTextColumntoStringList.sfn.sql new file mode 100644 index 000000000..0fabe1bc9 --- /dev/null +++ b/Source/tSQLt.Private_TableToTextColumntoStringList.sfn.sql @@ -0,0 +1,44 @@ +IF OBJECT_ID('tSQLt.Private_TableToTextColumntoStringList') IS NOT NULL DROP FUNCTION tSQLt.Private_TableToTextColumntoStringList; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_TableToTextColumntoStringList( + @tmpObjectId INT +) +RETURNS TABLE +AS +RETURN + SELECT + ( + SELECT + ',ISNULL('+ + CASE + WHEN type_name = 'DATETIME' THEN 'CONVERT(NVARCHAR(MAX),'+QUOTENAME(name)+',121)' + WHEN type_name = 'SMALLDATETIME' THEN 'CONVERT(NVARCHAR(16),'+QUOTENAME(name)+',121)' + WHEN type_name = 'FLOAT' THEN 'UPPER(CONVERT(NVARCHAR(MAX),'+QUOTENAME(name)+',2))' + WHEN type_name = 'IMAGE' THEN 'CONVERT(NVARCHAR(MAX),CAST('+QUOTENAME(name)+' AS VARBINARY(MAX)),1)' + WHEN type_name IN ('BINARY','VARBINARY') THEN 'CONVERT(NVARCHAR(MAX),'+QUOTENAME(name)+',1)' + WHEN type_name = 'UNIQUEIDENTIFIER' THEN 'LOWER(CONVERT(NVARCHAR(MAX),'+QUOTENAME(name)+'))' + WHEN type_name = 'TIMESTAMP' THEN 'CONVERT(NVARCHAR(MAX),CAST('+QUOTENAME(name)+' AS VARBINARY(MAX)),1)' + ELSE 'CAST('+QUOTENAME(name)+' AS NVARCHAR(MAX))' + END+ + ',''!NULL!'')' + FROM( + SELECT + C.column_id,UPPER(TS.name) type_name,C.name + FROM sys.columns C + JOIN sys.types TU + ON TU.user_type_id = C.user_type_id + LEFT JOIN sys.types TS + ON TS.user_type_id = TU.system_type_id + WHERE object_id = @tmpObjectId + AND column_id>1 + )X + ORDER BY column_id + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') [ColumnList]; + GO +---Build- +GO +--+REPLICATE(' ',LEN(C'+NN+')) +--',C'+NN+' = CASE WHEN LEN(C'+NN+')>L.MCW THEN LEFT(C'+NN+',(L.MCW-5)/2)+''<...>''+RIGHT(C'+NN+',(L.MCW-5)/2) ELSE LEFT(C'+NN+'+REPLICATE(' ',CASE WHEN L.MCW < L.L'+NN+' THEN L.MCW ELSE L.L'+NN+' END - LEN(C'+NN+')),9) END' \ No newline at end of file diff --git a/Source/tSQLt.Private_TableToTextNumberedColumnsWithSeparator.sfn.sql b/Source/tSQLt.Private_TableToTextNumberedColumnsWithSeparator.sfn.sql new file mode 100644 index 000000000..dd1b54524 --- /dev/null +++ b/Source/tSQLt.Private_TableToTextNumberedColumnsWithSeparator.sfn.sql @@ -0,0 +1,23 @@ +IF OBJECT_ID('tSQLt.Private_TableToTextNumberedColumnsWithSeparator') IS NOT NULL DROP FUNCTION tSQLt.Private_TableToTextNumberedColumnsWithSeparator; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_TableToTextNumberedColumnsWithSeparator( + @tmpObjectId INT +) +RETURNS TABLE +AS +RETURN + SELECT + ( + SELECT + 'sep+C'+ + RIGHT(CAST(10002+ROW_NUMBER()OVER(ORDER BY column_id) AS NVARCHAR(MAX)),4)+ + '+' + FROM sys.columns + WHERE object_id = @tmpObjectId + AND column_id>1 + ORDER BY column_id + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') [ColumnList]; + \ No newline at end of file diff --git a/Source/tSQLt.Private_UndoSingleTestDouble.ssp.sql b/Source/tSQLt.Private_UndoSingleTestDouble.ssp.sql new file mode 100644 index 000000000..068aa8f5e --- /dev/null +++ b/Source/tSQLt.Private_UndoSingleTestDouble.ssp.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.Private_UndoSingleTestDouble') IS NOT NULL DROP PROCEDURE tSQLt.Private_UndoSingleTestDouble; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_UndoSingleTestDouble + @SchemaName NVARCHAR(MAX), + @ObjectName NVARCHAR(MAX), + @OriginalName NVARCHAR(MAX) +AS +BEGIN + + + EXEC tSQLt.Private_RenameObject @SchemaName = @SchemaName, + @ObjectName = @ObjectName, + @NewName = @OriginalName; + +END; +GO diff --git a/Source/tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction.ssp.sql b/Source/tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction.ssp.sql index be16bf164..f3b7122e4 100644 --- a/Source/tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction.ssp.sql +++ b/Source/tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction.ssp.sql @@ -3,20 +3,44 @@ GO ---Build+ GO CREATE PROCEDURE tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction - @FunctionName NVARCHAR(MAX), - @FakeFunctionName NVARCHAR(MAX), - @FunctionObjectId INT OUTPUT, - @FakeFunctionObjectId INT OUTPUT, - @IsScalarFunction BIT OUTPUT + @FunctionName NVARCHAR(MAX), + @FakeFunctionName NVARCHAR(MAX) = NULL, + @FakeDataSource NVARCHAR(MAX) = NULL, + @FunctionObjectId INT = NULL OUTPUT, + @FakeFunctionObjectId INT = NULL OUTPUT, + @IsScalarFunction BIT = NULL OUTPUT AS BEGIN SET @FunctionObjectId = OBJECT_ID(@FunctionName); - SET @FakeFunctionObjectId = OBJECT_ID(@FakeFunctionName); IF(@FunctionObjectId IS NULL) BEGIN RAISERROR('%s does not exist!',16,10,@FunctionName); END; + + IF COALESCE(@FakeFunctionName, @FakeDataSource) IS NULL + BEGIN + RAISERROR ('Either @FakeFunctionName or @FakeDataSource must be provided', 16, 10); + END; + + IF (@FakeFunctionName IS NOT NULL AND @FakeDataSource IS NOT NULL ) + BEGIN + RAISERROR ('Both @FakeFunctionName and @FakeDataSource are valued. Please use only one.', 16, 10); + END; + + IF (@FakeDataSource IS NOT NULL ) + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(@FunctionName) and type in ('TF', 'IF', 'FT') + ) + BEGIN + RAISERROR('You can use @FakeDataSource only with Inline, Multi-Statement or CLR Table-Valued functions.', 16, 10); + END + + RETURN 0; + END + + SET @FakeFunctionObjectId = OBJECT_ID(@FakeFunctionName); IF(@FakeFunctionObjectId IS NULL) BEGIN RAISERROR('%s does not exist!',16,10,@FakeFunctionName); @@ -50,4 +74,3 @@ BEGIN END; END; GO - \ No newline at end of file diff --git a/Source/tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure.ssp.sql b/Source/tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure.ssp.sql new file mode 100644 index 000000000..608e394ed --- /dev/null +++ b/Source/tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure.ssp.sql @@ -0,0 +1,21 @@ +IF OBJECT_ID('tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure') IS NOT NULL DROP PROCEDURE tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure + @ProcedureName NVARCHAR(MAX) +AS +BEGIN + IF NOT EXISTS(SELECT 1 FROM sys.procedures WHERE object_id = OBJECT_ID(@ProcedureName)) + BEGIN + RAISERROR('Cannot use SpyProcedure on %s because the procedure does not exist', 16, 10, @ProcedureName) WITH NOWAIT; + END; + + IF (1020 < (SELECT COUNT(*) FROM sys.parameters WHERE object_id = OBJECT_ID(@ProcedureName))) + BEGIN + RAISERROR('Cannot use SpyProcedure on procedure %s because it contains more than 1020 parameters', 16, 10, @ProcedureName) WITH NOWAIT; + END; +END; +GO + + diff --git a/Source/tSQLt.RemoveAssemblyKey.ssp.sql b/Source/tSQLt.RemoveAssemblyKey.ssp.sql new file mode 100644 index 000000000..64de4b186 --- /dev/null +++ b/Source/tSQLt.RemoveAssemblyKey.ssp.sql @@ -0,0 +1,39 @@ +IF OBJECT_ID('tSQLt.RemoveAssemblyKey') IS NOT NULL DROP PROCEDURE tSQLt.RemoveAssemblyKey; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.RemoveAssemblyKey +AS +BEGIN + IF(NOT EXISTS(SELECT * FROM sys.fn_my_permissions(NULL,'server') AS FMP WHERE FMP.permission_name = 'CONTROL SERVER')) + BEGIN + RAISERROR('Only principals with CONTROL SERVER permission can execute this procedure.',16,10); + RETURN -1; + END; + + DECLARE @master_sys_sp_executesql NVARCHAR(MAX); SET @master_sys_sp_executesql = 'master.sys.sp_executesql'; + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + + IF SUSER_ID('tSQLtAssemblyKey') IS NOT NULL DROP LOGIN tSQLtAssemblyKey; + EXEC @master_sys_sp_executesql N'IF ASYMKEY_ID(''tSQLtAssemblyKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtAssemblyKey;'; + EXEC @master_sys_sp_executesql N'IF EXISTS(SELECT * FROM sys.assemblies WHERE name = ''tSQLtAssemblyKey'') DROP ASSEMBLY tSQLtAssemblyKey;'; + + DECLARE @cmd NVARCHAR(MAX); + IF(@ProductMajorVersion>=14) + BEGIN + DECLARE @TrustedHash NVARCHAR(MAX); + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + SELECT @TrustedHash = CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1); + SELECT @cmd = + 'IF EXISTS(SELECT 1 FROM sys.trusted_assemblies WHERE hash = ' + @TrustedHash +' AND description = ''tSQLt Ephemeral'')'+ + 'EXEC sys.sp_drop_trusted_assembly @hash = ' + @TrustedHash + ';'; + EXEC master.sys.sp_executesql @cmd; + END; + + +END; +GO +---Build- +GO diff --git a/Source/tSQLt.RemoveExternalAccessKey.ssp.sql b/Source/tSQLt.RemoveExternalAccessKey.ssp.sql index bd4505b13..72a05c629 100644 --- a/Source/tSQLt.RemoveExternalAccessKey.ssp.sql +++ b/Source/tSQLt.RemoveExternalAccessKey.ssp.sql @@ -5,17 +5,9 @@ GO CREATE PROCEDURE tSQLt.RemoveExternalAccessKey AS BEGIN - IF(NOT EXISTS(SELECT * FROM sys.fn_my_permissions(NULL,'server') AS FMP WHERE FMP.permission_name = 'CONTROL SERVER')) - BEGIN - RAISERROR('Only principals with CONTROL SERVER permission can execute this procedure.',16,10); - RETURN -1; - END; - - DECLARE @master_sys_sp_executesql NVARCHAR(MAX); SET @master_sys_sp_executesql = 'master.sys.sp_executesql'; - - IF SUSER_ID('tSQLtExternalAccessKey') IS NOT NULL DROP LOGIN tSQLtExternalAccessKey; - EXEC @master_sys_sp_executesql N'IF ASYMKEY_ID(''tSQLtExternalAccessKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtExternalAccessKey;'; - EXEC @master_sys_sp_executesql N'IF EXISTS(SELECT * FROM sys.assemblies WHERE name = ''tSQLtExternalAccessKey'') DROP ASSEMBLY tSQLtExternalAccessKey;'; + EXEC tSQLt.Private_Print @Message='tSQLt.RemoveExternalAccessKey is deprecated. Please use tSQLt.RemoveAssemblyKey instead.'; + EXEC tSQLt.RemoveAssemblyKey; +RETURN; END; GO ---Build- diff --git a/Source/tSQLt.SetSummaryError.ssp.sql b/Source/tSQLt.SetSummaryError.ssp.sql new file mode 100644 index 000000000..fb7ed47e7 --- /dev/null +++ b/Source/tSQLt.SetSummaryError.ssp.sql @@ -0,0 +1,17 @@ +IF OBJECT_ID('tSQLt.SetSummaryError') IS NOT NULL DROP PROCEDURE tSQLt.SetSummaryError; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.SetSummaryError + @SummaryError INT +AS +BEGIN + IF(@SummaryError NOT IN (0,1)) + BEGIN + RAISERROR('@SummaryError has to be 0 or 1, but it was: %i',16,10,@SummaryError); + END; + EXEC tSQLt.Private_SetConfiguration @Name = 'SummaryError', @Value = @SummaryError; +END; +GO +---Build- +GO \ No newline at end of file diff --git a/Source/tSQLt.SpyProcedure.ssp.sql b/Source/tSQLt.SpyProcedure.ssp.sql index a4b19cb73..ea05ed842 100644 --- a/Source/tSQLt.SpyProcedure.ssp.sql +++ b/Source/tSQLt.SpyProcedure.ssp.sql @@ -3,7 +3,8 @@ GO ---Build+ CREATE PROCEDURE tSQLt.SpyProcedure @ProcedureName NVARCHAR(MAX), - @CommandToExecute NVARCHAR(MAX) = NULL + @CommandToExecute NVARCHAR(MAX) = NULL, + @CallOriginal BIT = 0 AS BEGIN DECLARE @ProcedureObjectId INT; @@ -14,9 +15,33 @@ BEGIN DECLARE @LogTableName NVARCHAR(MAX); SELECT @LogTableName = QUOTENAME(OBJECT_SCHEMA_NAME(@ProcedureObjectId)) + '.' + QUOTENAME(OBJECT_NAME(@ProcedureObjectId)+'_SpyProcedureLog'); - EXEC tSQLt.Private_RenameObjectToUniqueNameUsingObjectId @ProcedureObjectId; + DECLARE @CreateProcedureStatement NVARCHAR(MAX); + DECLARE @CreateLogTableStatement NVARCHAR(MAX); - EXEC tSQLt.Private_CreateProcedureSpy @ProcedureObjectId, @ProcedureName, @LogTableName, @CommandToExecute; + DECLARE @NewNameOfOriginalObject NVARCHAR(MAX) = tSQLt.Private::CreateUniqueObjectName(); + + EXEC tSQLt.Private_GenerateCreateProcedureSpyStatement + @ProcedureObjectId = @ProcedureObjectId, + @OriginalProcedureName = @ProcedureName, + @UnquotedNewNameOfProcedure = @NewNameOfOriginalObject, + @LogTableName = @LogTableName, + @CommandToExecute = @CommandToExecute, + @CallOriginal = @CallOriginal, + @CreateProcedureStatement = @CreateProcedureStatement OUT, + @CreateLogTableStatement = @CreateLogTableStatement OUT; + + DECLARE @LogTableObjectId INT = OBJECT_ID(@LogTableName); + IF(@LogTableObjectId IS NOT NULL) + BEGIN + EXEC tSQLt.Private_RenameObjectToUniqueNameUsingObjectId @ObjectId = @LogTableObjectId; + END; + EXEC(@CreateLogTableStatement); + + EXEC tSQLt.Private_RenameObjectToUniqueNameUsingObjectId @ProcedureObjectId, @NewName = @NewNameOfOriginalObject OUTPUT; + EXEC(@CreateProcedureStatement); + + EXEC tSQLt.Private_MarktSQLtTempObject @ProcedureName, N'PROCEDURE', @NewNameOfOriginalObject; + EXEC tSQLt.Private_MarktSQLtTempObject @LogTableName, N'TABLE', NULL; RETURN 0; END; diff --git a/Source/tSQLt.TableToText.ssp.sql b/Source/tSQLt.TableToText.ssp.sql index d0ffecfc5..1342ec2f6 100644 --- a/Source/tSQLt.TableToText.ssp.sql +++ b/Source/tSQLt.TableToText.ssp.sql @@ -9,7 +9,50 @@ CREATE PROCEDURE tSQLt.TableToText @PrintOnlyColumnNameAliasList NVARCHAR(MAX) = NULL AS BEGIN - SET @txt = tSQLt.Private::TableToString(@TableName, @OrderBy, @PrintOnlyColumnNameAliasList); + IF(@TableName IS NULL) + BEGIN + RAISERROR('@TableName cannot be NULL',16,10) + END; + DECLARE @TmpTableName NVARCHAR(MAX) = 'tSQLt.[TableToText.Tmp.'+CAST(NEWID() AS NVARCHAR(MAX))+']'; + IF(OBJECT_ID(@TmpTableName)IS NOT NULL) EXEC('DROP TABLE '+@TmpTableName); + IF(OBJECT_ID('tempdb..[#tSQLt.TableToText.Str]')IS NOT NULL) DROP TABLE [#tSQLt.TableToText.Str]; + IF(OBJECT_ID('tempdb..[#tSQLt.TableToText.Str.Len]')IS NOT NULL) DROP TABLE [#tSQLt.TableToText.Str.Len]; + DECLARE @nl NVARCHAR(MAX) = 'CHAR(13)+CHAR(10)' + DECLARE @MaxColumnWidth INT = 155; + DECLARE @cmd NVARCHAR(MAX)= + 'SET NOCOUNT ON;'+ + 'SELECT * INTO '+@TmpTableName+' FROM (SELECT ROW_NUMBER()OVER(ORDER BY '+ + CASE WHEN NULLIF(LTRIM(@OrderBy),'') IS NULL + THEN '(SELECT 1)' + ELSE @OrderBy + END+')+2 [tSQLt.TableToText.OrderBy],* FROM '+@TableName+')X'+ + -- + CASE WHEN NULLIF(LTRIM(@PrintOnlyColumnNameAliasList),'') IS NULL + THEN '' + ELSE '([tSQLt.TableToText.OrderBy],'+@PrintOnlyColumnNameAliasList+')' + END+';'+ + 'EXEC tSQLt.Private_MarktSQLtTempObject '''+@TmpTableName+''',''TABLE'';'+ + 'DECLARE @tmpObjectId INT = OBJECT_ID('''+@TmpTableName+''');'+ + 'DECLARE @column_list NVARCHAR(MAX) = (SELECT ColumnList FROM tSQLt.Private_TableToTextColumnListQuotedAndNumbered(@tmpObjectId));'+ + 'DECLARE @cmd NVARCHAR(MAX)=''SELECT 1 no,''''|'''' sep''+@column_list+'' INTO [#tSQLt.TableToText.Str];'';'+ + 'SET @column_list = (SELECT ColumnList FROM tSQLt.Private_TableToTextColumntoStringList(@tmpObjectId));'+ + 'SET @cmd = @cmd + ''INSERT INTO [#tSQLt.TableToText.Str] SELECT [tSQLt.TableToText.OrderBy],''''|''''''+@column_list+'' FROM '+@TmpTableName+';'';'+ + 'SET @column_list = (SELECT ColumnList FROM tSQLt.Private_TableToTextColumnListMaxLenNumbered(@tmpObjectId));'+ + 'SET @cmd = @cmd + ''SELECT '+STR(@MaxColumnWidth)+' MCW''+@column_list+'' INTO [#tSQLt.TableToText.Str.Len] FROM [#tSQLt.TableToText.Str];'';'+ + 'SET @column_list = (SELECT ColumnList FROM tSQLt.Private_TableToTextColumnListAdjustWidth(@tmpObjectId));'+ + 'SET @cmd = @cmd + ''WITH LenAdjusted AS ('';'+ + 'SET @cmd = @cmd + ''SELECT no,sep''+@column_list+'' FROM [#tSQLt.TableToText.Str.Len] L CROSS JOIN [#tSQLt.TableToText.Str] C'';'+ + 'SET @cmd = @cmd + '' UNION ALL '';'+ + 'SET @column_list = (SELECT ColumnList FROM tSQLt.Private_TableToTextColumnListSeparatorLine(@tmpObjectId));'+ + 'SET @cmd = @cmd + ''SELECT 2,''''+'''' ''+@column_list+'' FROM [#tSQLt.TableToText.Str.Len] L'';'+ + 'SET @cmd = @cmd + '')'';'+ + 'SET @column_list = (SELECT ColumnList FROM tSQLt.Private_TableToTextNumberedColumnsWithSeparator(@tmpObjectId));'+ + 'SET @cmd = @cmd+''SELECT @txt=STUFF((SELECT '+@nl+'+''+@column_list+''sep FROM LenAdjusted ORDER BY no FOR XML PATH(''''''''),TYPE).value(''''.'''',''''NVARCHAR(MAX)''''),1,2,'''''''');'';'+ + -- 'PRINT @cmd;'+ + 'EXEC sys.sp_executesql @cmd,N''@txt NVARCHAR(MAX) OUT'',@txt OUT;'+ + 'DROP TABLE '+@TmpTableName+';'; + -- PRINT @cmd; + EXEC sys.sp_executesql @cmd,N'@txt NVARCHAR(MAX) OUT',@txt OUT; END; GO ---Build- diff --git a/Source/tSQLt.TempDropStatement.mdl.sql b/Source/tSQLt.TempDropStatement.mdl.sql new file mode 100644 index 000000000..e98e40ff5 --- /dev/null +++ b/Source/tSQLt.TempDropStatement.mdl.sql @@ -0,0 +1,2 @@ +---Build+ +DECLARE @ClassName NVARCHAR(MAX) = 'tSQLt';/*--DROPSTATEMENT--*/ diff --git a/Source/tSQLt.TestClass.user.sql b/Source/tSQLt.TestClass.user.sql new file mode 100644 index 000000000..9b081a060 --- /dev/null +++ b/Source/tSQLt.TestClass.user.sql @@ -0,0 +1,8 @@ +---Build+ +GO +IF (USER_ID('tSQLt.TestClass') IS NULL) +BEGIN + CREATE USER [tSQLt.TestClass] WITHOUT LOGIN; +END; +GO +---Build- \ No newline at end of file diff --git a/Source/tSQLt.TestClasses.view.sql b/Source/tSQLt.TestClasses.view.sql new file mode 100644 index 000000000..adc04fb8c --- /dev/null +++ b/Source/tSQLt.TestClasses.view.sql @@ -0,0 +1,14 @@ +IF OBJECT_ID('tSQLt.TestClasses') IS NOT NULL DROP VIEW tSQLt.TestClasses; +GO +---Build+ +GO +CREATE VIEW tSQLt.TestClasses +AS + SELECT s.name AS Name, s.schema_id AS SchemaId + FROM sys.schemas s + LEFT JOIN sys.extended_properties ep + ON ep.major_id = s.schema_id + WHERE ep.name = N'tSQLt.TestClass' + OR s.principal_id = USER_ID('tSQLt.TestClass'); +GO +---Build- diff --git a/Source/tSQLt.Tests.view.sql b/Source/tSQLt.Tests.view.sql new file mode 100644 index 000000000..7f878ecc2 --- /dev/null +++ b/Source/tSQLt.Tests.view.sql @@ -0,0 +1,13 @@ +IF OBJECT_ID('tSQLt.Tests') IS NOT NULL DROP VIEW tSQLt.Tests; +GO +---Build+ +GO +CREATE VIEW tSQLt.Tests +AS + SELECT classes.SchemaId, classes.Name AS TestClassName, + procs.object_id AS ObjectId, procs.name AS Name + FROM tSQLt.TestClasses classes + JOIN sys.procedures procs ON classes.SchemaId = procs.schema_id + WHERE LOWER(procs.name) LIKE 'test%'; +GO +---Build- diff --git a/Source/tSQLt.UndoTestDoubles.ssp.sql b/Source/tSQLt.UndoTestDoubles.ssp.sql new file mode 100644 index 000000000..59a7bb63a --- /dev/null +++ b/Source/tSQLt.UndoTestDoubles.ssp.sql @@ -0,0 +1,239 @@ +IF OBJECT_ID('tSQLt.UndoTestDoubles') IS NOT NULL DROP PROCEDURE tSQLt.UndoTestDoubles; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.UndoTestDoubles + @Force BIT = 0 +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + DECLARE @ErrorMessageTableList NVARCHAR(MAX); + DECLARE @ErrorMessage NVARCHAR(MAX) = ''; + + + /*-- Two non-temp objects, the first of which should be renamed to the second --*/ + SELECT @ErrorMessage = @ErrorMessage + ISNULL(REPLACE('Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. (%s)','%s',Collisions.List),'') + FROM + ( + SELECT + STUFF ( + ( + SELECT + ', ' + QUOTENAME(OBJECT_SCHEMA_NAME(TestDouble.object_id))+'.'+QUOTENAME(TestDouble.name) + FROM tSQLt.Private_RenamedObjectLog AS ROL + JOIN sys.objects AS TestDouble + ON TestDouble.object_id = OBJECT_ID(QUOTENAME(OBJECT_SCHEMA_NAME(ROL.ObjectId))+'.'+QUOTENAME(PARSENAME(ROL.OriginalName,1))) + LEFT JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = TestDouble.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + WHERE EP.value IS NULL + ORDER BY 1 + FOR XML PATH (''), TYPE + ).value('.','NVARCHAR(MAX)'), + 1,2,'') + ) Collisions(List) + + /*-- Attempting to rename two or more non-temp objects to the same name --*/ + + IF(EXISTS( + SELECT O.schema_id, ROL.OriginalName, COUNT(1) cnt + FROM tSQLt.Private_RenamedObjectLog ROL + JOIN sys.objects O + ON ROL.ObjectId = O.object_id + LEFT JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = O.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + WHERE EP.value IS NULL + GROUP BY O.schema_id, ROL.OriginalName + HAVING COUNT(1)>1 + )) + BEGIN + WITH S AS( + SELECT + C.Id, + C.OriginalName, + C.CurrentName, + C.SchemaName + FROM( + SELECT ROL.OriginalName, ROL.Id, O.name CurrentName, SCHEMA_NAME(O.schema_id) SchemaName, COUNT(1)OVER(PARTITION BY O.schema_id, ROL.OriginalName) Cnt + FROM tSQLt.Private_RenamedObjectLog ROL + JOIN sys.objects O + ON ROL.ObjectId = O.object_id + LEFT JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = O.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + WHERE EP.value IS NULL + )C + WHERE C.Cnt>1 + ), + ErrorTableLists AS( + SELECT + '{'+C.CList+'}-->' + QUOTENAME(SO.SchemaName)+'.'+QUOTENAME(PARSENAME(SO.OriginalName,1)) ErrorTableList, + QUOTENAME(SO.SchemaName)+'.'+QUOTENAME(PARSENAME(SO.OriginalName,1)) FullOriginalName + FROM (SELECT DISTINCT SchemaName, OriginalName FROM S) SO + CROSS APPLY ( + SELECT ( + STUFF( + ( + SELECT ', '+QUOTENAME(SC.CurrentName) + FROM S AS SC + WHERE SC.OriginalName = SO.OriginalName + AND SC.SchemaName = SO.SchemaName + ORDER BY SC.Id + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'), + 1,2,'') + ) CList + )C + ) + SELECT @ErrorMessageTableList = ( + STUFF( + ( + SELECT '; '+ETL.ErrorTableList + FROM ErrorTableLists ETL + ORDER BY ETL.FullOriginalName + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'), + 1,2,'' + ) + ); + SELECT @ErrorMessage = @ErrorMessage + REPLACE('Attempting to rename two or more objects to the same name. Use @Force = 1 to override, only first object of each rename survives. (%s)','%s',@ErrorMessageTableList); + END; + IF(@ErrorMessage <> '') + BEGIN + IF (@Force = 1) + BEGIN + SET @ErrorMessage = 'WARNING: @Force has been set to 1. Overriding the following error(s):'+@ErrorMessage; + EXEC tSQLt.Private_Print @Message = @ErrorMessage; + END; + ELSE + BEGIN + RAISERROR(@ErrorMessage,16,10); + END; + END; + + + + + SELECT TOP(0)A.* INTO #RenamedObjects FROM tSQLt.Private_RenamedObjectLog A RIGHT JOIN tSQLt.Private_RenamedObjectLog X ON 1=0; + + + + + + BEGIN TRAN; + DELETE FROM tSQLt.Private_RenamedObjectLog OUTPUT Deleted.* INTO #RenamedObjects; + + WITH MarkedTestDoubles AS + ( + SELECT + TempO.Name, + SCHEMA_NAME(TempO.schema_id) SchemaName, + TempO.type ObjectType + FROM sys.objects TempO + JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = TempO.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + ) + SELECT @cmd = + ( + SELECT + DC.cmd+';' + FROM MarkedTestDoubles MTD + CROSS APPLY tSQLt.Private_GetDropItemCmd(QUOTENAME(MTD.SchemaName)+'.'+QUOTENAME(MTD.Name),MTD.ObjectType) DC + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); + + SELECT @cmd = + ( + SELECT + DC.cmd+';' + FROM( + SELECT + * + FROM( + SELECT + ROL.OriginalName, + O.object_id, + O.type ObjectType, + SCHEMA_NAME(O.schema_id) SchemaName, + O.name CurrentName, + ROW_NUMBER()OVER(PARTITION BY O.schema_id, ROL.OriginalName ORDER BY ROL.Id) RN + FROM #RenamedObjects AS ROL + JOIN sys.objects O + ON O.object_id = ROL.ObjectId + )ROLI + WHERE ROLI.RN>1 + )Deletables + CROSS APPLY tSQLt.Private_GetDropItemCmd(QUOTENAME(Deletables.SchemaName)+'.'+QUOTENAME(Deletables.CurrentName),Deletables.ObjectType) DC + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); + + + WITH LL AS + ( + SELECT + ROL.Id, + ParentROL.Id ParentId, + ISNULL(ParentROL.Id,ROL.Id) SortId, + ROL.ObjectId, + OBJECT_SCHEMA_NAME(ROL.ObjectId) SchemaName, + OBJECT_NAME(ROL.ObjectId) CurrentName, + PARSENAME(ROL.OriginalName,1) OriginalName + FROM #RenamedObjects ROL + JOIN sys.objects O + ON ROL.ObjectId = O.object_id + LEFT JOIN #RenamedObjects ParentROL + ON O.parent_object_id = ParentROL.ObjectId + ), + L AS + ( + SELECT + LL.Id, + LL.ParentId, + LL.SortId, + LL.ObjectId, + LL.SchemaName, + LL.CurrentName, + LL.OriginalName, + FakeO.type ObjectType, + CASE WHEN EP.value IS NOT NULL THEN 1 ELSE 0 END IsTempObject + FROM LL + LEFT JOIN sys.objects FakeO + ON FakeO.object_id = OBJECT_ID(QUOTENAME(LL.SchemaName)+'.'+QUOTENAME(LL.OriginalName)) + LEFT JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = FakeO.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + ) + SELECT @cmd = + ( + SELECT + ISNULL(CASE + WHEN L.ParentId IS NULL THEN DC.cmd+';' + ELSE NULL + END,'')+ + 'EXEC tSQLt.Private_RenameObject '''+L.SchemaName+''','''+L.CurrentName+''','''+L.OriginalName+''';' + FROM L + CROSS APPLY tSQLt.Private_GetDropItemCmd(QUOTENAME(L.SchemaName)+'.'+QUOTENAME(L.OriginalName),L.ObjectType) DC + ORDER BY L.SortId DESC, L.Id ASC + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); + + + COMMIT; +END; +GO + diff --git a/Source/tSQLt.Uninstall.ssp.sql b/Source/tSQLt.Uninstall.ssp.sql new file mode 100644 index 000000000..6ca3d5172 --- /dev/null +++ b/Source/tSQLt.Uninstall.ssp.sql @@ -0,0 +1,17 @@ +IF OBJECT_ID('tSQLt.Uninstall') IS NOT NULL DROP PROCEDURE tSQLt.Uninstall; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Uninstall +AS +BEGIN + + EXEC tSQLt.DropClass @ClassName = 'tSQLt'; + + IF(EXISTS(SELECT 1 FROM sys.assemblies WHERE name = 'tSQLtCLR'))DROP ASSEMBLY tSQLtCLR; + + IF USER_ID('tSQLt.TestClass') IS NOT NULL DROP USER [tSQLt.TestClass]; + +END; +GO +---Build- diff --git a/Source/tSQLt._Footer.sql b/Source/tSQLt._Footer.sql index aec93ba60..aebfc8e22 100644 --- a/Source/tSQLt._Footer.sql +++ b/Source/tSQLt._Footer.sql @@ -1,3 +1,4 @@ +---Build-n ---Build+ GO SET NOCOUNT ON; diff --git a/Source/tSQLt._Header.sql b/Source/tSQLt._Header.sql index 9a501fd28..69c43ba9a 100644 --- a/Source/tSQLt._Header.sql +++ b/Source/tSQLt._Header.sql @@ -1,3 +1,4 @@ +---Build-h ---Build+ /* Copyright 2011 tSQLt @@ -16,20 +17,4 @@ */ DECLARE @Msg NVARCHAR(MAX);SELECT @Msg = 'Installed at '+CONVERT(NVARCHAR,GETDATE(),121);RAISERROR(@Msg,0,1); GO - -IF TYPE_ID('tSQLt.Private') IS NOT NULL DROP TYPE tSQLt.Private; -IF TYPE_ID('tSQLtPrivate') IS NOT NULL DROP TYPE tSQLtPrivate; -GO -IF OBJECT_ID('tSQLt.DropClass') IS NOT NULL - EXEC tSQLt.DropClass tSQLt; -GO - -IF EXISTS (SELECT 1 FROM sys.assemblies WHERE name = 'tSQLtCLR') - DROP ASSEMBLY tSQLtCLR; -GO - -CREATE SCHEMA tSQLt; -GO -SET QUOTED_IDENTIFIER ON; -GO ---Build- \ No newline at end of file diff --git a/Source/tSQLt.class.sql b/Source/tSQLt.class.sql index f20b85632..69012dd19 100644 --- a/Source/tSQLt.class.sql +++ b/Source/tSQLt.class.sql @@ -1,19 +1,15 @@ ---Build+ GO CREATE TABLE tSQLt.TestResult( - Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, + Id INT IDENTITY(1,1) CONSTRAINT [PK:tSQLt.TestResult] PRIMARY KEY CLUSTERED, Class NVARCHAR(MAX) NOT NULL, TestCase NVARCHAR(MAX) NOT NULL, Name AS (QUOTENAME(Class) + '.' + QUOTENAME(TestCase)), - TranName NVARCHAR(MAX) NOT NULL, + TranName NVARCHAR(MAX) NULL, Result NVARCHAR(MAX) NULL, Msg NVARCHAR(MAX) NULL, - TestStartTime DATETIME NOT NULL CONSTRAINT [DF:TestResult(TestStartTime)] DEFAULT GETDATE(), - TestEndTime DATETIME NULL -); -GO -CREATE TABLE tSQLt.TestMessage( - Msg NVARCHAR(MAX) + TestStartTime DATETIME2 NOT NULL CONSTRAINT [DF:TestResult(TestStartTime)] DEFAULT SYSDATETIME(), + TestEndTime DATETIME2 NULL ); GO CREATE TABLE tSQLt.Run_LastExecution( @@ -22,8 +18,6 @@ CREATE TABLE tSQLt.Run_LastExecution( LoginTime DATETIME ); GO -CREATE TABLE tSQLt.Private_ExpectException(i INT); -GO CREATE PROCEDURE tSQLt.Private_Print @Message NVARCHAR(MAX), @Severity INT = 0 @@ -59,7 +53,8 @@ CREATE PROCEDURE tSQLt.Private_PrintXML @Message XML AS BEGIN - SELECT @Message FOR XML PATH('');--Required together with ":XML ON" sqlcmd statement to allow more than 1mb to be returned + SET NOCOUNT ON; + SELECT CAST(@Message AS XML);--Required together with ":XML ON" sqlcmd statement to allow more than 1mb to be returned RETURN 0; END; GO @@ -126,21 +121,15 @@ GO ---------------------------------------------------------------------- CREATE FUNCTION tSQLt.Private_GetLastTestNameIfNotProvided(@TestName NVARCHAR(MAX)) -RETURNS NVARCHAR(MAX) +RETURNS TABLE AS -BEGIN - IF(LTRIM(ISNULL(@TestName,'')) = '') - BEGIN - SELECT @TestName = TestName - FROM tSQLt.Run_LastExecution le - JOIN sys.dm_exec_sessions es - ON le.SessionId = es.session_id - AND le.LoginTime = es.login_time - WHERE es.session_id = @@SPID; - END - - RETURN @TestName; -END +RETURN + SELECT CASE WHEN (LTRIM(ISNULL(@TestName,'')) = '') THEN LE.TestName ELSE @TestName END TestName + FROM tSQLt.Run_LastExecution LE + RIGHT JOIN sys.dm_exec_sessions ES + ON LE.SessionId = ES.session_id + AND LE.LoginTime = ES.login_time + WHERE ES.session_id = @@SPID; GO CREATE PROCEDURE tSQLt.Private_SaveTestNameForSession @@ -160,31 +149,14 @@ END GO ---------------------------------------------------------------------- -CREATE VIEW tSQLt.TestClasses -AS - SELECT s.name AS Name, s.schema_id AS SchemaId - FROM sys.extended_properties ep - JOIN sys.schemas s - ON ep.major_id = s.schema_id - WHERE ep.name = N'tSQLt.TestClass'; -GO - -CREATE VIEW tSQLt.Tests -AS - SELECT classes.SchemaId, classes.Name AS TestClassName, - procs.object_id AS ObjectId, procs.name AS Name - FROM tSQLt.TestClasses classes - JOIN sys.procedures procs ON classes.SchemaId = procs.schema_id - WHERE LOWER(procs.name) LIKE 'test%'; -GO - CREATE FUNCTION tSQLt.TestCaseSummary() RETURNS TABLE AS -RETURN WITH A(Cnt, SuccessCnt, FailCnt, ErrorCnt) AS ( +RETURN WITH A(Cnt, SuccessCnt, SkippedCnt, FailCnt, ErrorCnt) AS ( SELECT COUNT(1), ISNULL(SUM(CASE WHEN Result = 'Success' THEN 1 ELSE 0 END), 0), + ISNULL(SUM(CASE WHEN Result = 'Skipped' THEN 1 ELSE 0 END), 0), ISNULL(SUM(CASE WHEN Result = 'Failure' THEN 1 ELSE 0 END), 0), ISNULL(SUM(CASE WHEN Result = 'Error' THEN 1 ELSE 0 END), 0) FROM tSQLt.TestResult @@ -192,28 +164,12 @@ RETURN WITH A(Cnt, SuccessCnt, FailCnt, ErrorCnt) AS ( ) SELECT 'Test Case Summary: ' + CAST(Cnt AS NVARCHAR) + ' test case(s) executed, '+ CAST(SuccessCnt AS NVARCHAR) + ' succeeded, '+ + CAST(SkippedCnt AS NVARCHAR) + ' skipped, '+ CAST(FailCnt AS NVARCHAR) + ' failed, '+ CAST(ErrorCnt AS NVARCHAR) + ' errored.' Msg,* FROM A; GO -CREATE PROCEDURE tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure - @ProcedureName NVARCHAR(MAX) -AS -BEGIN - IF NOT EXISTS(SELECT 1 FROM sys.procedures WHERE object_id = OBJECT_ID(@ProcedureName)) - BEGIN - RAISERROR('Cannot use SpyProcedure on %s because the procedure does not exist', 16, 10, @ProcedureName) WITH NOWAIT; - END; - - IF (1020 < (SELECT COUNT(*) FROM sys.parameters WHERE object_id = OBJECT_ID(@ProcedureName))) - BEGIN - RAISERROR('Cannot use SpyProcedure on procedure %s because it contains more than 1020 parameters', 16, 10, @ProcedureName) WITH NOWAIT; - END; -END; -GO - - CREATE PROCEDURE tSQLt.AssertEquals @Expected SQL_VARIANT, @Actual SQL_VARIANT, @@ -281,7 +237,7 @@ BEGIN WHERE class_desc = 'OBJECT_OR_COLUMN' AND major_id = OBJECT_ID(@SchemaName + '.' + @TableName) AND minor_id = 0 - AND name = 'tSQLt.FakeTable_OrgTableName'); + AND name = 'tSQLt.Private_TestDouble_OrgObjectName'); END; GO @@ -294,7 +250,7 @@ AS WHERE class_desc = 'OBJECT_OR_COLUMN' AND major_id = @TableObjectId AND minor_id = 0 - AND name = 'tSQLt.FakeTable_OrgTableName'; + AND name = 'tSQLt.Private_TestDouble_OrgObjectName'; GO @@ -538,13 +494,3 @@ RETURN ORDER BY ord GO -CREATE PROCEDURE tSQLt.Uninstall -AS -BEGIN - DROP TYPE tSQLt.Private; - - EXEC tSQLt.DropClass 'tSQLt'; - - DROP ASSEMBLY tSQLtCLR; -END; -GO diff --git a/Source/tSQLt.schema.sql b/Source/tSQLt.schema.sql new file mode 100644 index 000000000..e951a75be --- /dev/null +++ b/Source/tSQLt.schema.sql @@ -0,0 +1,10 @@ +---Build+ +IF EXISTS (SELECT 1 FROM sys.assemblies WHERE name = 'tSQLtCLR') + DROP ASSEMBLY tSQLtCLR; +GO + +CREATE SCHEMA tSQLt; +GO +SET QUOTED_IDENTIFIER ON; +GO +---Build- diff --git a/Source/tSQLtCLR.mdl.sql b/Source/tSQLtCLR.mdl.sql index 4f8c4b3a1..8dfadf3a8 100644 --- a/Source/tSQLtCLR.mdl.sql +++ b/Source/tSQLtCLR.mdl.sql @@ -1,4 +1,9 @@ ---Build+ GO -CREATE ASSEMBLY [tSQLtCLR] AUTHORIZATION [dbo] FROM 0x000000 WITH PERMISSION_SET = SAFE; +DECLARE @cmd NVARCHAR(MAX) = ''; +SET @cmd = @cmd + --<-- force NVARCHAR(MAX) +'CREATE ASSEMBLY [tSQLtCLR] AUTHORIZATION [dbo] FROM '+ +'0x000000'+ +' WITH PERMISSION_SET = SAFE;'; +EXEC(@cmd); GO diff --git a/Source/tSQLtCLR_CreateProcs.sql b/Source/tSQLtCLR_CreateProcs.sql index b15259899..14aed3ff4 100644 --- a/Source/tSQLtCLR_CreateProcs.sql +++ b/Source/tSQLtCLR_CreateProcs.sql @@ -13,6 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. */ +IF OBJECT_ID('tSQLt.ResultSetFilter') IS NOT NULL DROP PROCEDURE tSQLt.ResultSetFilter; +IF OBJECT_ID('tSQLt.AssertResultSetsHaveSameMetaData') IS NOT NULL DROP PROCEDURE tSQLt.AssertResultSetsHaveSameMetaData; +IF OBJECT_ID('tSQLt.NewConnection') IS NOT NULL DROP PROCEDURE tSQLt.NewConnection; +IF OBJECT_ID('tSQLt.CaptureOutput') IS NOT NULL DROP PROCEDURE tSQLt.CaptureOutput; +IF OBJECT_ID('tSQLt.SuppressOutput') IS NOT NULL DROP PROCEDURE tSQLt.SuppressOutput; +IF OBJECT_ID('tSQLt.Private_GetAnnotationList') IS NOT NULL DROP FUNCTION tSQLt.Private_GetAnnotationList; +IF TYPE_ID('tSQLt.[Private]') IS NOT NULL DROP TYPE tSQLt.[Private]; + +GO + GO ---Build+ GO @@ -45,3 +55,9 @@ AS EXTERNAL NAME tSQLtCLR.[tSQLtCLR.StoredProcedures].SuppressOutput; GO +CREATE FUNCTION tSQLt.Private_GetAnnotationList(@ProcedureDefinition NVARCHAR(MAX)) + RETURNS TABLE(AnnotationNo INT, Annotation NVARCHAR(MAX)) + AS EXTERNAL NAME tSQLtCLR.[tSQLtCLR.Annotations].GetAnnotationList; + +GO + diff --git a/Source/tSQLtDropBuildOrder.txt b/Source/tSQLtDropBuildOrder.txt new file mode 100644 index 000000000..2f04a46c9 --- /dev/null +++ b/Source/tSQLtDropBuildOrder.txt @@ -0,0 +1 @@ +tSQLt.DropClass.ssp.sql diff --git a/Source/testoutput.sql b/Source/testoutput.sql index 915072773..925003c82 100644 --- a/Source/testoutput.sql +++ b/Source/testoutput.sql @@ -95,7 +95,7 @@ RETURN SELECT 'CONSTRAINT ' + name + ' FOREIGN KEY (' + ON refCol.object_id = c.referenced_object_id AND refCol.column_id = c.referenced_column_id LEFT JOIN sys.extended_properties e - ON e.name = 'tSQLt.FakeTable_OrgTableName' + ON e.name = 'tSQLt.Private_TestDouble_OrgObjectName' AND e.value = OBJECT_NAME(c.referenced_object_id) JOIN sys.tables refTab ON COALESCE(e.major_id,refCol.object_id) = refTab.object_id diff --git a/TestUtil/BuildOrder.txt b/TestUtil/BuildOrder.txt index 161d9d007..7da4b891e 100644 --- a/TestUtil/BuildOrder.txt +++ b/TestUtil/BuildOrder.txt @@ -1,5 +1,4 @@ tSQLt_testutil.class.sql -../Build/temp/CreateTestUtilAssembly.sql -../Build/temp/GetUnsignedEmptyBytes.sql +../Build/temp/tSQLtTests/CreateTestUtilAssembly.sql +../Build/temp/tSQLtTests/GetUnsignedEmptyBytes.sql tSQLtTestUtilCLR_CreateItems.sql -tSQLt_testutil_test.class.sql diff --git a/TestUtil/TestUtil.sqlproj b/TestUtil/TestUtil.sqlproj new file mode 100644 index 000000000..b42e5cb1f --- /dev/null +++ b/TestUtil/TestUtil.sqlproj @@ -0,0 +1,13 @@ + + + + + TestUtil + {175C322C-24CB-461E-93CD-B47DB6141A87} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + 1033, CI + + + + + \ No newline at end of file diff --git a/TestUtil/TestUtil.ssmssqlproj b/TestUtil/TestUtil.ssmssqlproj deleted file mode 100644 index 58a1e1648..000000000 --- a/TestUtil/TestUtil.ssmssqlproj +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - 2014-11-24T18:18:42.7948609-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_Dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - GetUnsignedEmptyBytes.mdl.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLtTestUtilCLR.mdl.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLtTestUtilCLR_CreateItems.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt_testutil.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt_testutil_test.class.sql - - - - - - - - - - BuildOrder.txt - - - - - \ No newline at end of file diff --git a/TestUtil/tSQLtTestUtilCLR_CreateItems.sql b/TestUtil/tSQLtTestUtilCLR_CreateItems.sql index 300b8dcea..143ec61ef 100644 --- a/TestUtil/tSQLtTestUtilCLR_CreateItems.sql +++ b/TestUtil/tSQLtTestUtilCLR_CreateItems.sql @@ -24,3 +24,6 @@ GO CREATE FUNCTION tSQLt_testutil.AnEmptyClrTvf(@p1 NVARCHAR(MAX), @p2 NVARCHAR(MAX))RETURNS TABLE(id INT, val NVARCHAR(MAX)) AS EXTERNAL NAME tSQLtTestUtilCLR.[tSQLtTestUtilCLR.ClrFunctions].AnEmptyClrTvf; GO +CREATE PROCEDURE tSQLt_testutil.AClrSsp + AS EXTERNAL NAME tSQLtTestUtilCLR.[tSQLtTestUtilCLR.ClrStoredProcedures].AClrSsp; +GO diff --git a/TestUtil/tSQLt_testutil.class.sql b/TestUtil/tSQLt_testutil.class.sql index 44068317f..6de51f761 100644 --- a/TestUtil/tSQLt_testutil.class.sql +++ b/TestUtil/tSQLt_testutil.class.sql @@ -159,29 +159,48 @@ BEGIN DECLARE @TestClassName NVARCHAR(MAX); DECLARE @TestProcName NVARCHAR(MAX); - DECLARE tests CURSOR LOCAL FAST_FORWARD FOR + DECLARE testsWithExtendedProperty CURSOR LOCAL FAST_FORWARD FOR SELECT DISTINCT s.name AS testClassName FROM sys.extended_properties ep JOIN sys.schemas s ON ep.major_id = s.schema_id WHERE ep.name = N'tSQLt.TestClass'; - OPEN tests; + OPEN testsWithExtendedProperty; - FETCH NEXT FROM tests INTO @TestClassName; + FETCH NEXT FROM testsWithExtendedProperty INTO @TestClassName; WHILE @@FETCH_STATUS = 0 BEGIN EXEC sp_dropextendedproperty @name = 'tSQLt.TestClass', @level0type = 'SCHEMA', @level0name = @TestClassName; - FETCH NEXT FROM tests INTO @TestClassName; + FETCH NEXT FROM testsWithExtendedProperty INTO @TestClassName; END; - CLOSE tests; - DEALLOCATE tests; + CLOSE testsWithExtendedProperty; + DEALLOCATE testsWithExtendedProperty; + + DECLARE testsWithSchemaClassification CURSOR LOCAL FAST_FORWARD FOR + SELECT DISTINCT s.name AS testClassName + FROM sys.schemas s + WHERE s.principal_id = USER_ID('tSQLt.TestClass'); + + OPEN testsWithSchemaClassification; + + FETCH NEXT FROM testsWithSchemaClassification INTO @TestClassName; + WHILE @@FETCH_STATUS = 0 + BEGIN + DECLARE @cmd NVARCHAR(MAX) = 'ALTER AUTHORIZATION ON SCHEMA::'+QUOTENAME(@TestClassName)+' TO dbo;'; + EXEC(@cmd); + FETCH NEXT FROM testsWithSchemaClassification INTO @TestClassName; + END; + + CLOSE testsWithSchemaClassification; + DEALLOCATE testsWithSchemaClassification; END; GO + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- @@ -263,6 +282,249 @@ BEGIN END END; GO +CREATE PROCEDURE tSQLt_testutil.AssertTestSkipped + @TestName NVARCHAR(MAX), + @ExpectedMessage NVARCHAR(MAX) = NULL +AS +BEGIN + DECLARE @Result NVARCHAR(MAX); + DECLARE @Msg NVARCHAR(MAX); + + EXEC tSQLt_testutil.CaptureTestResult @TestName, @Result OUTPUT, @Msg OUTPUT; + + IF(@Result <> 'Skipped') + BEGIN + EXEC tSQLt.Fail 'Expected test to be skipped. Instead it resulted in ',@Result,'. The Message is: "',@Msg,'"'; + END + + IF(@ExpectedMessage IS NOT NULL) + BEGIN + EXEC tSQLt.AssertLike @ExpectedMessage,@Msg,'Incorrect Skip Message:'; + END +END; +GO +CREATE PROCEDURE tSQLt_testutil.LogMultiRunResult + @TestCaseSet NVARCHAR(MAX) +AS +BEGIN + SELECT + SUM(CASE WHEN Result = 'Success' THEN 1 ELSE 0 END) Success, + SUM(CASE WHEN Result = 'Skipped' THEN 1 ELSE 0 END) Skipped, + SUM(CASE WHEN Result = 'Failure' THEN 1 ELSE 0 END) Failure, + SUM(CASE WHEN Result = 'Error' THEN 1 ELSE 0 END) [Error], + @TestCaseSet TestCaseSet + INTO #MRL + FROM tSQLt.TestResult; + SELECT * FROM #MRL AS M; + INSERT INTO tSQLt_testutil.MultiRunLog(Success,Skipped,Failure,Error,TestCaseSet) + SELECT * FROM #MRL AS M; +END; +GO +CREATE PROCEDURE tSQLt_testutil.CheckMultiRunResults + @noError INT = 0 +AS +BEGIN + SELECT * FROM tSQLt_testutil.MultiRunLog AS MRL; + DECLARE @Severity INT = CASE WHEN @noError <> 0 THEN 0 ELSE 16 END; + IF(EXISTS(SELECT * FROM tSQLt_testutil.MultiRunLog AS MRL WHERE MRL.Failure<>0 OR MRL.Error<>0)) + BEGIN + EXEC tSQLt.Private_Print @Message = 'tSQLt execution with failures or errors detected.', @Severity = @Severity + END; + IF(NOT EXISTS(SELECT * FROM tSQLt_testutil.MultiRunLog AS MRL)) + BEGIN + EXEC tSQLt.Private_Print @Message = 'MultiRunLog is empty.', @Severity = @Severity + END; + IF(EXISTS(SELECT * FROM tSQLt_testutil.MultiRunLog AS MRL WHERE MRL.Success = 0 AND MRL.Skipped = 0)) + BEGIN + EXEC tSQLt.Private_Print @Message = 'MultiRunLog contains Run without tests.', @Severity = @Severity + END; +END; +GO +CREATE FUNCTION tSQLt_testutil.GenerateRandomPassword(@R UNIQUEIDENTIFIER) +RETURNS TABLE +AS +RETURN +SELECT CAST(@R AS NVARCHAR(MAX))+'[P0l1cyRe7u1r3mnt$]' PW; +GO +CREATE PROCEDURE tSQLt_testutil.AssertTableContainsString + @Table NVARCHAR(MAX), + @Column NVARCHAR(MAX), + @Pattern NVARCHAR(MAX) +AS +BEGIN + CREATE TABLE #TheRowExists(Yes INT); + DECLARE @cmd NVARCHAR(MAX) = N'INSERT INTO #TheRowExists SELECT 1 FROM '+@Table+' WHERE '+@Column+' LIKE @Pattern'; + EXEC sp_executesql @cmd,N'@Pattern NVARCHAR(MAX)',@Pattern; + + IF NOT EXISTS(SELECT 1 FROM #TheRowExists) + BEGIN + DECLARE @Msg NVARCHAR(MAX) = 'Expected to find '+@Pattern+' in '+@Table+' but didn''t.'; + EXEC tSQLt.AssertEmptyTable -- roundabout way to get the table content printed + @TableName = @Table, + @Message = @Msg; + EXEC tSQLt.Fail 'Expected to find ',@Pattern,' in ',@Table,' but didn''t. Table was empty.'; + END; +END; +GO +CREATE PROCEDURE tSQLt_testutil.WaitForMS + @milliseconds INT +AS +BEGIN + --WAITFOR DELAY might return significantly earlier than one would expect. Hence this workaround... + DECLARE @EndTime DATETIME2 = DATEADD(MILLISECOND,@milliseconds,SYSDATETIME()); + WHILE(SYSDATETIME()<@EndTime) + BEGIN + WAITFOR DELAY '00:00:00.010'; + END; +END; +GO +CREATE PROCEDURE tSQLt_testutil.PrepMultiRunLogTable +AS +BEGIN + IF OBJECT_ID('tSQLt_testutil.MultiRunLog') IS NOT NULL DROP TABLE tSQLt_testutil.MultiRunLog; + CREATE TABLE tSQLt_testutil.MultiRunLog + ( + id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, + Success INT, + Skipped INT, + Failure INT, + [Error] INT, + TestCaseSet NVARCHAR(MAX) + ); +END; +GO +/*CreateBuildLogStart*/ +GO +CREATE PROCEDURE tSQLt_testutil.CreateBuildLog + @TableName NVARCHAR(MAX) +AS +BEGIN + IF(OBJECT_ID(@TableName) IS NOT NULL)EXEC('DROP TABLE '+@TableName); + DECLARE @cmd NVARCHAR(MAX) = + ' + CREATE TABLE '+@TableName+' + ( + [id] [int] NOT NULL IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + [Success] [int] NULL, + [Skipped] [int] NULL, + [Failure] [int] NULL, + [Error] [int] NULL, + [TestCaseSet] NVARCHAR(MAX) NULL, + [RunGroup] NVARCHAR(MAX) NULL, + [DatabaseName] NVARCHAR(MAX) NULL, + [Version] VARCHAR(14) NULL, + [ClrVersion] NVARCHAR(4000) NULL, + [ClrSigningKey] VARBINARY(8000) NULL, + [InstalledOnSqlVersion] NUMERIC(10, 2) NULL, + [SqlVersion] NUMERIC(10, 2) NULL, + [SqlBuild] NUMERIC(10, 2) NULL, + [SqlEdition] NVARCHAR(128) NULL, + [HostPlatform] NVARCHAR(256) NULL + ); + '; + EXEC(@cmd); + SET @cmd = 'GRANT INSERT ON '+@TableName+' TO PUBLIC;'; + IF(PARSENAME(@TableName,3) IS NOT NULL) + BEGIN + SET @cmd = PARSENAME(@TableName,3)+'.sys.sp_executesql N'''+@cmd+''',N'''''; + END; + EXEC(@cmd); +END; +GO +/*CreateBuildLogEnd*/ +GO +CREATE PROCEDURE tSQLt_testutil.StoreBuildLog + @TableName NVARCHAR(MAX), + @RunGroup NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX) = + (SELECT QUOTENAME(C.name)+',' + FROM sys.columns AS C + WHERE C.object_id = OBJECT_ID('tSQLt_testutil.MultiRunLog') + AND C.is_identity = 0 + ORDER BY C.column_id + FOR XML PATH,TYPE).value('.','NVARCHAR(MAX)'); + SET @cmd = 'INSERT INTO '+@TableName+' SELECT '+@cmd+'RG,DB,I.* FROM tSQLt_testutil.MultiRunLog CROSS JOIN tSQLt.Info() I RIGHT JOIN (VALUES('''+@RunGroup+''',DB_NAME()))XX(RG,DB) ON 1=1;' + + EXEC(@cmd); +END; +GO +CREATE PROCEDURE tSQLt_testutil.CheckBuildLog + @TableName NVARCHAR(MAX) +AS +BEGIN + IF OBJECT_ID('tSQLt_testutil.LocalBuildLogTemp') IS NOT NULL DROP TABLE tSQLt_testutil.LocalBuildLogTemp; + EXEC('SELECT * INTO tSQLt_testutil.LocalBuildLogTemp FROM '+@TableName+';'); + + IF OBJECT_ID('tSQLt_testutil.LocalBuildLogTempFormatted') IS NOT NULL DROP TABLE tSQLt_testutil.LocalBuildLogTempFormatted; + DECLARE @cmd NVARCHAR(MAX) = + ( + SELECT 'SELECT '+ + 'CAST(REPLICATE('' '','+CAST(MAX(idML) AS NVARCHAR(MAX))+'-LEN(id))+CAST(id AS VARCHAR(MAX)) AS CHAR('+CAST(MAX(idML)+1 AS NVARCHAR(MAX))+'))id,'+ + 'CAST(REPLICATE('' '','+CAST(MAX(SuccessML) AS NVARCHAR(MAX))+'-LEN(Success))+CAST(Success AS VARCHAR(MAX)) AS CHAR('+CAST(MAX(SuccessML)+1 AS NVARCHAR(MAX))+'))Success,'+ + 'CAST(REPLICATE('' '','+CAST(MAX(SkippedML) AS NVARCHAR(MAX))+'-LEN(Skipped))+CAST(Skipped AS VARCHAR(MAX)) AS CHAR('+CAST(MAX(SkippedML)+1 AS NVARCHAR(MAX))+'))Skipped,'+ + 'CAST(REPLICATE('' '','+CAST(MAX(FailureML) AS NVARCHAR(MAX))+'-LEN(Failure))+CAST(Failure AS VARCHAR(MAX)) AS CHAR('+CAST(MAX(FailureML)+1 AS NVARCHAR(MAX))+'))Failure,'+ + 'CAST(REPLICATE('' '','+CAST(MAX(ErrorML) AS NVARCHAR(MAX))+'-LEN(Error))+CAST(Error AS VARCHAR(MAX)) AS CHAR('+CAST(MAX(ErrorML)+1 AS NVARCHAR(MAX))+'))Error,'+ + 'CAST(TestCaseSet AS CHAR('+CAST(MAX(TestCaseSetML) AS NVARCHAR(MAX))+'))TestCaseSet,'+ + 'CAST(RunGroup AS CHAR('+CAST(MAX(RunGroupML) AS NVARCHAR(MAX))+'))RunGroup,'+ + 'CAST(DatabaseName AS CHAR('+CAST(MAX(DatabaseNameML) AS NVARCHAR(MAX))+'))DatabaseName, '+ + 'CAST([Version] AS CHAR('+CAST(MAX(VersionML) AS NVARCHAR(MAX))+'))[Version], '+ + 'CAST([ClrVersion] AS CHAR('+CAST(MAX(ClrVersionML) AS NVARCHAR(MAX))+'))[ClrVersion], '+ + 'CONVERT(CHAR('+CAST(MAX(ClrSigningKeyML) AS NVARCHAR(MAX))+'),[ClrSigningKey],1)[ClrSigningKey], '+ + 'CAST([InstalledOnSqlVersion] AS CHAR('+CAST(MAX(InstalledOnSqlVersionML) AS NVARCHAR(MAX))+'))[InstalledOnSqlVersion], '+ + 'CAST([SqlVersion] AS CHAR('+CAST(MAX(SqlVersionML) AS NVARCHAR(MAX))+'))[SqlVersion], '+ + 'CAST([SqlBuild] AS CHAR('+CAST(MAX(SqlBuildML) AS NVARCHAR(MAX))+'))[SqlBuild], '+ + 'CAST([SqlEdition] AS CHAR('+CAST(MAX(SqlEditionML) AS NVARCHAR(MAX))+'))[SqlEdition], '+ + 'CAST([HostPlatform] AS CHAR('+CAST(MAX(HostPlatformML) AS NVARCHAR(MAX))+'))[HostPlatform] '+ + 'INTO tSQLt_testutil.LocalBuildLogTempFormatted FROM tSQLt_testutil.LocalBuildLogTemp' + FROM + ( + SELECT + MAX(LEN(CAST(id AS VARCHAR(MAX)))) idML, + MAX(LEN(CAST(Success AS VARCHAR(MAX)))) SuccessML, + MAX(LEN(CAST(Skipped AS VARCHAR(MAX)))) SkippedML, + MAX(LEN(CAST(Failure AS VARCHAR(MAX)))) FailureML, + MAX(LEN(CAST(Error AS VARCHAR(MAX)))) ErrorML, + 2+MAX(LEN(TestCaseSet)) TestCaseSetML, + 2+MAX(LEN(RunGroup)) RunGroupML, + 2+MAX(LEN(DatabaseName)) DatabaseNameML, + MAX(LEN(CAST([Version] AS VARCHAR(MAX)))) VersionML, + MAX(LEN(CAST([ClrVersion] AS VARCHAR(MAX)))) ClrVersionML, + MAX(LEN(CONVERT(NVARCHAR(MAX),[ClrSigningKey],1))) ClrSigningKeyML, + MAX(LEN(CAST([InstalledOnSqlVersion] AS VARCHAR(MAX)))) InstalledOnSqlVersionML, + MAX(LEN(CAST([SqlVersion] AS VARCHAR(MAX)))) SqlVersionML, + MAX(LEN(CAST([SqlBuild] AS VARCHAR(MAX)))) SqlBuildML, + MAX(LEN(CAST([SqlEdition] AS VARCHAR(MAX)))) SqlEditionML, + MAX(LEN(CAST([HostPlatform] AS VARCHAR(MAX)))) HostPlatformML + FROM tSQLt_testutil.LocalBuildLogTemp + UNION ALL SELECT 2,7,7,7,5,11,8,12,1,1,1,1,1,1,1,1 + )X + ); + EXEC(@cmd); + DECLARE @BuildLog NVARCHAR(MAX); + EXEC tSQLt.TableToText @txt = @BuildLog OUT, @TableName = 'tSQLt_testutil.LocalBuildLogTempFormatted',@OrderBy = 'id' + EXEC tSQLt.Private_Print @Message = @BuildLog; + + IF(EXISTS(SELECT * FROM tSQLt_testutil.LocalBuildLogTemp AS MRL WHERE MRL.Failure<>0 OR MRL.Error<>0)) + BEGIN + EXEC tSQLt.Private_Print @Message = 'tSQLt execution with failures or errors detected.', @Severity = 16 + END; + IF(NOT EXISTS(SELECT * FROM tSQLt_testutil.LocalBuildLogTemp AS MRL)) + BEGIN + EXEC tSQLt.Private_Print @Message = 'BuildLog is empty.', @Severity = 16 + END; + IF(EXISTS(SELECT * FROM tSQLt_testutil.LocalBuildLogTemp AS MRL WHERE MRL.Success IS NULL)) + BEGIN + EXEC tSQLt.Private_Print @Message = 'BuildLog contains Test Suite Group without tests.', @Severity = 16 + END; + IF(EXISTS(SELECT * FROM tSQLt_testutil.LocalBuildLogTemp AS MRL WHERE MRL.Success = 0 AND MRL.Skipped = 0)) + BEGIN + EXEC tSQLt.Private_Print @Message = 'BuildLog contains Run without tests.', @Severity = 16 + END; +END; +GO + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- diff --git a/TestUtil/tSQLt_testutil_test.class.sql b/TestUtil/tSQLt_testutil_test.class.sql deleted file mode 100644 index 1daae093e..000000000 --- a/TestUtil/tSQLt_testutil_test.class.sql +++ /dev/null @@ -1,280 +0,0 @@ -GO -EXEC tSQLt.NewTestClass 'tSQLt_testutil_test'; -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeNoEqual can be serialized] -AS -BEGIN - DECLARE @inst1 tSQLt_testutil.DataTypeNoEqual; - DECLARE @inst1bin BINARY(5); - DECLARE @inst2 tSQLt_testutil.DataTypeNoEqual; - DECLARE @inst2bin BINARY(5); - - SET @inst1 = '1'; - SET @inst1bin = CAST(@inst1 AS BINARY(5)); - EXEC tSQLt.AssertEquals 0x0001000000, @inst1bin; - - SET @inst2 = CAST(@inst1bin AS tSQLt_testutil.DataTypeNoEqual); - SET @inst2bin = CAST(@inst2 AS BINARY(5)); - EXEC tSQLt.AssertEquals 0x0001000000, @inst2bin; - -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeNoEqual has constant ToString()] -AS -BEGIN - DECLARE @inst1 tSQLt_testutil.DataTypeNoEqual; - DECLARE @inst1str NVARCHAR(MAX); - DECLARE @inst2 tSQLt_testutil.DataTypeNoEqual; - DECLARE @inst2str NVARCHAR(MAX); - - SET @inst1 = '1'; - SET @inst2 = '2'; - - SET @inst1str = @inst1.ToString(); - SET @inst2str = @inst2.ToString(); - - EXEC tSQLt.AssertEqualsString @inst1str,@inst2str; -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeNoEqual cannot compare] -AS -BEGIN - DECLARE @Message NVARCHAR(MAX); - SET @Message = ''; - - BEGIN TRY - EXEC('IF(CAST( ''1'' AS tSQLt_testutil.DataTypeNoEqual) = CAST( ''2'' AS tSQLt_testutil.DataTypeNoEqual)) PRINT 1;') - END TRY - BEGIN CATCH - SELECT @Message = ERROR_MESSAGE() - END CATCH - - EXEC tSQLt.AssertEqualsString 'Invalid operator for data type. Operator equals equal to, type equals DataTypeNoEqual.',@Message; - -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeNoEqual cannot GROUP BY] -AS -BEGIN - DECLARE @Message NVARCHAR(MAX); - SET @Message = ''; - - CREATE TABLE tSQLt_testutil_test.tmp1( - id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, - dtne tSQLt_testutil.DataTypeNoEqual NULL - ); - - INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); - INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); - - BEGIN TRY - EXEC('SELECT dtne,COUNT(1) Cnt FROM tSQLt_testutil_test.tmp1 GROUP BY dtne;'); - END TRY - BEGIN CATCH - SELECT @Message = ERROR_MESSAGE() - END CATCH - - EXEC tSQLt.AssertEqualsString 'The type "DataTypeNoEqual" is not comparable. It cannot be used in the GROUP BY clause.',@Message; - -END -GO ------------------------------------------------------------------------------------------------------------- -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual can be serialized] -AS -BEGIN - DECLARE @inst1 tSQLt_testutil.DataTypeWithEqual; - DECLARE @inst1bin BINARY(5); - DECLARE @inst2 tSQLt_testutil.DataTypeWithEqual; - DECLARE @inst2bin BINARY(5); - - SET @inst1 = '1'; - SET @inst1bin = CAST(@inst1 AS BINARY(5)); - EXEC tSQLt.AssertEquals 0x0001000000, @inst1bin; - - SET @inst2 = CAST(@inst1bin AS tSQLt_testutil.DataTypeWithEqual); - SET @inst2bin = CAST(@inst2 AS BINARY(5)); - EXEC tSQLt.AssertEquals 0x0001000000, @inst2bin; - -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual has constant ToString()] -AS -BEGIN - DECLARE @inst1 tSQLt_testutil.DataTypeWithEqual; - DECLARE @inst1str NVARCHAR(MAX); - DECLARE @inst2 tSQLt_testutil.DataTypeWithEqual; - DECLARE @inst2str NVARCHAR(MAX); - - SET @inst1 = '1'; - SET @inst2 = '2'; - - SET @inst1str = @inst1.ToString(); - SET @inst2str = @inst2.ToString(); - - EXEC tSQLt.AssertEqualsString @inst1str,@inst2str; -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual has CompareTo (but we can't use it...)] -AS -BEGIN - DECLARE @Message NVARCHAR(MAX); - SET @Message = ''; - DECLARE @inst1 tSQLt_testutil.DataTypeWithEqual; - - SET @inst1 = '1'; - - BEGIN TRY - PRINT @inst1.CompareTo(CAST(@inst1 AS BINARY(5))); - END TRY - BEGIN CATCH - SELECT @Message = ERROR_MESSAGE() - END CATCH - - EXEC tSQLt.AssertLike '%Object is not a DataTypeWithEqual.%',@Message; - -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual cannot compare] -AS -BEGIN - DECLARE @Message NVARCHAR(MAX); - SET @Message = ''; - - BEGIN TRY - EXEC('IF(CAST( ''1'' AS tSQLt_testutil.DataTypeWithEqual) = CAST( ''2'' AS tSQLt_testutil.DataTypeWithEqual)) PRINT 1;') - END TRY - BEGIN CATCH - SELECT @Message = ERROR_MESSAGE() - END CATCH - - EXEC tSQLt.AssertEqualsString 'Invalid operator for data type. Operator equals equal to, type equals DataTypeWithEqual.',@Message; - -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual cannot GROUP BY] -AS -BEGIN - DECLARE @Message NVARCHAR(MAX); - SET @Message = ''; - - CREATE TABLE tSQLt_testutil_test.tmp1( - id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, - dtne tSQLt_testutil.DataTypeWithEqual NULL - ); - - INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); - INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); - - BEGIN TRY - EXEC('SELECT dtne,COUNT(1) Cnt FROM tSQLt_testutil_test.tmp1 GROUP BY dtne;'); - END TRY - BEGIN CATCH - SELECT @Message = ERROR_MESSAGE() - END CATCH - - EXEC tSQLt.AssertEqualsString 'The type "DataTypeWithEqual" is not comparable. It cannot be used in the GROUP BY clause.',@Message; - -END -GO ------------------------------------------------------------------------------------------------------------- -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered can be serialized] -AS -BEGIN - DECLARE @inst1 tSQLt_testutil.DataTypeByteOrdered; - DECLARE @inst1bin BINARY(5); - DECLARE @inst2 tSQLt_testutil.DataTypeByteOrdered; - DECLARE @inst2bin BINARY(5); - - SET @inst1 = '1'; - SET @inst1bin = CAST(@inst1 AS BINARY(5)); - EXEC tSQLt.AssertEquals 0x0001000000, @inst1bin; - - SET @inst2 = CAST(@inst1bin AS tSQLt_testutil.DataTypeByteOrdered); - SET @inst2bin = CAST(@inst2 AS BINARY(5)); - EXEC tSQLt.AssertEquals 0x0001000000, @inst2bin; - -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered has constant ToString()] -AS -BEGIN - DECLARE @inst1 tSQLt_testutil.DataTypeByteOrdered; - DECLARE @inst1str NVARCHAR(MAX); - DECLARE @inst2 tSQLt_testutil.DataTypeByteOrdered; - DECLARE @inst2str NVARCHAR(MAX); - - SET @inst1 = '1'; - SET @inst2 = '2'; - - SET @inst1str = @inst1.ToString(); - SET @inst2str = @inst2.ToString(); - - EXEC tSQLt.AssertEqualsString @inst1str,@inst2str; -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered has CompareTo (but we can't use it...)] -AS -BEGIN - DECLARE @Message NVARCHAR(MAX); - SET @Message = ''; - DECLARE @inst1 tSQLt_testutil.DataTypeByteOrdered; - - SET @inst1 = '1'; - - BEGIN TRY - PRINT @inst1.CompareTo(CAST(@inst1 AS BINARY(5))); - END TRY - BEGIN CATCH - SELECT @Message = ERROR_MESSAGE() - END CATCH - - EXEC tSQLt.AssertLike '%Object is not a DataTypeByteOrdered.%',@Message; - -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered can compare] -AS -BEGIN - DECLARE @Message NVARCHAR(MAX); - SET @Message = ''; - - IF(CAST( '1' AS tSQLt_testutil.DataTypeByteOrdered) = CAST( '2' AS tSQLt_testutil.DataTypeByteOrdered)) - BEGIN - EXEC tSQLt.Fail '1 and 2 should not be equal...'; - END - -END -GO -CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered can GROUP BY] -AS -BEGIN - DECLARE @Message NVARCHAR(MAX); - SET @Message = ''; - - CREATE TABLE tSQLt_testutil_test.tmp1( - id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, - dtne tSQLt_testutil.DataTypeByteOrdered NULL - ); - - INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); - INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); - INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('2'); - INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('2'); - INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('2'); - - SELECT CAST(dtne AS BINARY(5)) dtne,COUNT(1) cnt - INTO #actual - FROM tSQLt_testutil_test.tmp1 GROUP BY dtne; - - SELECT TOP(0)* INTO #expected FROM #actual; - INSERT INTO #expected(dtne, cnt) - SELECT 0x0001000000, 2 - UNION ALL - SELECT 0x0002000000, 3; - - EXEC tSQLt.AssertEqualsTable '#expected','#actual'; -END -GO - diff --git a/TestUtilTests/TestUtilTests.sqlproj b/TestUtilTests/TestUtilTests.sqlproj new file mode 100644 index 000000000..289bd6fb5 --- /dev/null +++ b/TestUtilTests/TestUtilTests.sqlproj @@ -0,0 +1,13 @@ + + + + + TestUtilTests + {89304D64-1E01-4AA2-8A2A-646042F96D81} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + 1033, CI + + + + + \ No newline at end of file diff --git a/TestUtilTests/tSQLt_testutil_test.class.sql b/TestUtilTests/tSQLt_testutil_test.class.sql new file mode 100644 index 000000000..7035c2fcd --- /dev/null +++ b/TestUtilTests/tSQLt_testutil_test.class.sql @@ -0,0 +1,507 @@ +GO +EXEC tSQLt.NewTestClass 'tSQLt_testutil_test'; +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeNoEqual can be serialized] +AS +BEGIN + DECLARE @inst1 tSQLt_testutil.DataTypeNoEqual; + DECLARE @inst1bin BINARY(5); + DECLARE @inst2 tSQLt_testutil.DataTypeNoEqual; + DECLARE @inst2bin BINARY(5); + + SET @inst1 = '1'; + SET @inst1bin = CAST(@inst1 AS BINARY(5)); + EXEC tSQLt.AssertEquals 0x0001000000, @inst1bin; + + SET @inst2 = CAST(@inst1bin AS tSQLt_testutil.DataTypeNoEqual); + SET @inst2bin = CAST(@inst2 AS BINARY(5)); + EXEC tSQLt.AssertEquals 0x0001000000, @inst2bin; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeNoEqual has constant ToString()] +AS +BEGIN + DECLARE @inst1 tSQLt_testutil.DataTypeNoEqual; + DECLARE @inst1str NVARCHAR(MAX); + DECLARE @inst2 tSQLt_testutil.DataTypeNoEqual; + DECLARE @inst2str NVARCHAR(MAX); + + SET @inst1 = '1'; + SET @inst2 = '2'; + + SET @inst1str = @inst1.ToString(); + SET @inst2str = @inst2.ToString(); + + EXEC tSQLt.AssertEqualsString @inst1str,@inst2str; +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeNoEqual cannot compare] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = ''; + + BEGIN TRY + EXEC('IF(CAST( ''1'' AS tSQLt_testutil.DataTypeNoEqual) = CAST( ''2'' AS tSQLt_testutil.DataTypeNoEqual)) PRINT 1;') + END TRY + BEGIN CATCH + SELECT @Message = ERROR_MESSAGE() + END CATCH + + EXEC tSQLt.AssertEqualsString 'Invalid operator for data type. Operator equals equal to, type equals DataTypeNoEqual.',@Message; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeNoEqual cannot GROUP BY] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = ''; + + CREATE TABLE tSQLt_testutil_test.tmp1( + id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, + dtne tSQLt_testutil.DataTypeNoEqual NULL + ); + + INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); + INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); + + BEGIN TRY + EXEC('SELECT dtne,COUNT(1) Cnt FROM tSQLt_testutil_test.tmp1 GROUP BY dtne;'); + END TRY + BEGIN CATCH + SELECT @Message = ERROR_MESSAGE() + END CATCH + + EXEC tSQLt.AssertEqualsString 'The type "DataTypeNoEqual" is not comparable. It cannot be used in the GROUP BY clause.',@Message; + +END +GO +------------------------------------------------------------------------------------------------------------ +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual can be serialized] +AS +BEGIN + DECLARE @inst1 tSQLt_testutil.DataTypeWithEqual; + DECLARE @inst1bin BINARY(5); + DECLARE @inst2 tSQLt_testutil.DataTypeWithEqual; + DECLARE @inst2bin BINARY(5); + + SET @inst1 = '1'; + SET @inst1bin = CAST(@inst1 AS BINARY(5)); + EXEC tSQLt.AssertEquals 0x0001000000, @inst1bin; + + SET @inst2 = CAST(@inst1bin AS tSQLt_testutil.DataTypeWithEqual); + SET @inst2bin = CAST(@inst2 AS BINARY(5)); + EXEC tSQLt.AssertEquals 0x0001000000, @inst2bin; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual has constant ToString()] +AS +BEGIN + DECLARE @inst1 tSQLt_testutil.DataTypeWithEqual; + DECLARE @inst1str NVARCHAR(MAX); + DECLARE @inst2 tSQLt_testutil.DataTypeWithEqual; + DECLARE @inst2str NVARCHAR(MAX); + + SET @inst1 = '1'; + SET @inst2 = '2'; + + SET @inst1str = @inst1.ToString(); + SET @inst2str = @inst2.ToString(); + + EXEC tSQLt.AssertEqualsString @inst1str,@inst2str; +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual has CompareTo (but we can't use it...)] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = ''; + DECLARE @inst1 tSQLt_testutil.DataTypeWithEqual; + + SET @inst1 = '1'; + + BEGIN TRY + PRINT @inst1.CompareTo(CAST(@inst1 AS BINARY(5))); + END TRY + BEGIN CATCH + SELECT @Message = ERROR_MESSAGE() + END CATCH + + EXEC tSQLt.AssertLike '%Object is not a DataTypeWithEqual.%',@Message; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual cannot compare] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = ''; + + BEGIN TRY + EXEC('IF(CAST( ''1'' AS tSQLt_testutil.DataTypeWithEqual) = CAST( ''2'' AS tSQLt_testutil.DataTypeWithEqual)) PRINT 1;') + END TRY + BEGIN CATCH + SELECT @Message = ERROR_MESSAGE() + END CATCH + + EXEC tSQLt.AssertEqualsString 'Invalid operator for data type. Operator equals equal to, type equals DataTypeWithEqual.',@Message; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeWithEqual cannot GROUP BY] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = ''; + + CREATE TABLE tSQLt_testutil_test.tmp1( + id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, + dtne tSQLt_testutil.DataTypeWithEqual NULL + ); + + INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); + INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); + + BEGIN TRY + EXEC('SELECT dtne,COUNT(1) Cnt FROM tSQLt_testutil_test.tmp1 GROUP BY dtne;'); + END TRY + BEGIN CATCH + SELECT @Message = ERROR_MESSAGE() + END CATCH + + EXEC tSQLt.AssertEqualsString 'The type "DataTypeWithEqual" is not comparable. It cannot be used in the GROUP BY clause.',@Message; + +END +GO +------------------------------------------------------------------------------------------------------------ +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered can be serialized] +AS +BEGIN + DECLARE @inst1 tSQLt_testutil.DataTypeByteOrdered; + DECLARE @inst1bin BINARY(5); + DECLARE @inst2 tSQLt_testutil.DataTypeByteOrdered; + DECLARE @inst2bin BINARY(5); + + SET @inst1 = '1'; + SET @inst1bin = CAST(@inst1 AS BINARY(5)); + EXEC tSQLt.AssertEquals 0x0001000000, @inst1bin; + + SET @inst2 = CAST(@inst1bin AS tSQLt_testutil.DataTypeByteOrdered); + SET @inst2bin = CAST(@inst2 AS BINARY(5)); + EXEC tSQLt.AssertEquals 0x0001000000, @inst2bin; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered has ToString()] +AS +BEGIN + DECLARE @inst1 tSQLt_testutil.DataTypeByteOrdered; + DECLARE @inst1str NVARCHAR(MAX); + DECLARE @inst2 tSQLt_testutil.DataTypeByteOrdered; + DECLARE @inst2str NVARCHAR(MAX); + + SET @inst1 = '13'; + + SET @inst1str = @inst1.ToString(); + + EXEC tSQLt.AssertEqualsString '<>',@inst1str; +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered has CompareTo (but we can't use it...)] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = ''; + DECLARE @inst1 tSQLt_testutil.DataTypeByteOrdered; + + SET @inst1 = '1'; + + BEGIN TRY + PRINT @inst1.CompareTo(CAST(@inst1 AS BINARY(5))); + END TRY + BEGIN CATCH + SELECT @Message = ERROR_MESSAGE() + END CATCH + + EXEC tSQLt.AssertLike '%Object is not a DataTypeByteOrdered.%',@Message; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered can compare] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = ''; + + IF(CAST( '1' AS tSQLt_testutil.DataTypeByteOrdered) = CAST( '2' AS tSQLt_testutil.DataTypeByteOrdered)) + BEGIN + EXEC tSQLt.Fail '1 and 2 should not be equal...'; + END + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test DataTypeByteOrdered can GROUP BY] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = ''; + + CREATE TABLE tSQLt_testutil_test.tmp1( + id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, + dtne tSQLt_testutil.DataTypeByteOrdered NULL + ); + + INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); + INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('1'); + INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('2'); + INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('2'); + INSERT INTO tSQLt_testutil_test.tmp1(dtne)VALUES('2'); + + SELECT CAST(dtne AS BINARY(5)) dtne,COUNT(1) cnt + INTO #actual + FROM tSQLt_testutil_test.tmp1 GROUP BY dtne; + + SELECT TOP(0)* INTO #expected FROM #actual; + INSERT INTO #expected(dtne, cnt) + SELECT 0x0001000000, 2 + UNION ALL + SELECT 0x0002000000, 3; + + EXEC tSQLt.AssertEqualsTable '#expected','#actual'; +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable] + @identity INT = 0 +AS +BEGIN + EXEC tSQLt_testutil.PrepMultiRunLogTable + EXEC tSQLt.FakeTable @TableName = 'tSQLt_testutil.MultiRunLog', @Identity = @identity, @ComputedColumns = 0, @Defaults = 0; +END; +GO +CREATE PROCEDURE tSQLt_testutil_test.[test CheckMultiRunResults throws error if a test error exists in the log] +AS +BEGIN + EXEC tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Error)VALUES(1); + + SELECT TOP(0)* + INTO #Actual + FROM tSQLt_testutil.MultiRunLog AS MRL; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt execution with failures or errors detected.', @ExpectedSeverity = 16, @ExpectedState = 10; + + INSERT INTO #Actual + EXEC tSQLt_testutil.CheckMultiRunResults; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test CheckMultiRunResults throws error if a test failure exists in the log] +AS +BEGIN + EXEC tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Failure)VALUES(1); + + SELECT TOP(0)* + INTO #Actual + FROM tSQLt_testutil.MultiRunLog AS MRL; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt execution with failures or errors detected.', @ExpectedSeverity = 16, @ExpectedState = 10; + + INSERT INTO #Actual + EXEC tSQLt_testutil.CheckMultiRunResults; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test CheckMultiRunResults throws no error if all tests in the log succeeded] +AS +BEGIN + EXEC tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Success)VALUES(1); + + SELECT TOP(0)* + INTO #Actual + FROM tSQLt_testutil.MultiRunLog AS MRL; + + EXEC tSQLt.ExpectNoException; + + INSERT INTO #Actual + EXEC tSQLt_testutil.CheckMultiRunResults; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test CheckMultiRunResults returns contents of Log as resultset] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; + EXEC tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(id,Success,Skipped,Failure,Error,TestCaseSet) + VALUES + (1,17,9,1,3,'row 1'),(2,4,6,7,5,'row 2'); + + SELECT TOP(0)* + INTO #Actual + FROM tSQLt_testutil.MultiRunLog AS MRL; + + INSERT INTO #Actual + EXEC tSQLt_testutil.CheckMultiRunResults; + + EXEC tSQLt.AssertEqualsTable 'tSQLt_testutil.MultiRunLog','#Actual'; +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test CheckMultiRunResults throws error if the log is empty] +AS +BEGIN + EXEC tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + + SELECT TOP(0)* + INTO #Actual + FROM tSQLt_testutil.MultiRunLog AS MRL; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'MultiRunLog is empty.', @ExpectedSeverity = 16, @ExpectedState = 10; + + INSERT INTO #Actual + EXEC tSQLt_testutil.CheckMultiRunResults; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test CheckMultiRunResults throws error if the log contains empty run] +AS +BEGIN + EXEC tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(id,Success,Skipped,Failure,Error,TestCaseSet) + VALUES(42,0,0,0,0,'some run'); + + SELECT TOP(0)* + INTO #Actual + FROM tSQLt_testutil.MultiRunLog AS MRL; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'MultiRunLog contains Run without tests.', @ExpectedSeverity = 16, @ExpectedState = 10; + + INSERT INTO #Actual + EXEC tSQLt_testutil.CheckMultiRunResults; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test CheckMultiRunResults doesn't throw error if @noError=1] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; + EXEC tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + + SELECT TOP(0)* + INTO #Ignore + FROM tSQLt_testutil.MultiRunLog AS MRL; + + + INSERT INTO #Ignore + EXEC tSQLt_testutil.CheckMultiRunResults; + + INSERT INTO tSQLt_testutil.MultiRunLog(Success,Skipped,Failure,Error) + VALUES(0,0,0,0); + + INSERT INTO #Ignore + EXEC tSQLt_testutil.CheckMultiRunResults; + + INSERT INTO tSQLt_testutil.MultiRunLog(Success,Skipped,Failure,Error) + VALUES(0,0,2,3); + + INSERT INTO #Ignore + EXEC tSQLt_testutil.CheckMultiRunResults; + + SELECT Message, Severity INTO #Actual FROM tSQLt.Private_Print_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('tSQLt execution with failures or errors detected.',0), + ('MultiRunLog is empty.',0), + ('MultiRunLog contains Run without tests.',0); +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[ptest CheckMultiRunResults doesn't throw error if] + @success INT = 0, + @skipped INT = 0, + @failed INT = 0, + @errored INT = 0 +AS +BEGIN + EXEC tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Success,Skipped,Failure,Error) + VALUES(@success,@skipped,@failed,@errored); + + SELECT TOP(0)* + INTO #Actual + FROM tSQLt_testutil.MultiRunLog AS MRL; + + EXEC tSQLt.ExpectNoException; + + INSERT INTO #Actual + EXEC tSQLt_testutil.CheckMultiRunResults; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test CheckMultiRunResults doesn't throw error if a success exists] +AS +BEGIN + EXEC tSQLt_testutil_test.[ptest CheckMultiRunResults doesn't throw error if] @success=1; +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test CheckMultiRunResults doesn't throw error if a skip exists] +AS +BEGIN + EXEC tSQLt_testutil_test.[ptest CheckMultiRunResults doesn't throw error if] @skipped=1; +END +GO +CREATE PROCEDURE tSQLt_testutil_test.[test LogMultiRunResult captures all results] +AS +BEGIN + EXEC tSQLt_testutil_test.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult', @Identity = 0, @ComputedColumns = 0, @Defaults = 0; + INSERT INTO tSQLt.TestResult(Result) + VALUES('Success'),('Success'),('Success'),('Success'), + ('Skipped'),('Skipped'),('Skipped'), + ('Failure'),('Failure'), + ('Error'); + + EXEC tSQLt.SuppressOutput @command = 'EXEC tSQLt_testutil.LogMultiRunResult @TestCaseSet=''MyTestingSet'';'; + + SELECT * + INTO #Actual + FROM tSQLt_testutil.MultiRunLog AS MRL; + + SELECT TOP(0) + A.Success, + A.Skipped, + A.Failure, + A.Error, + A.TestCaseSet + INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES(4,3,2,1,'MyTestingSet'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE tSQLt_testutil_test.[test tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses removes TestClass classifications] +AS +BEGIN + EXEC ('CREATE SCHEMA tSQLtTestDummyA AUTHORIZATION [tSQLt.TestClass];'); + EXEC ('CREATE SCHEMA tSQLtTestDummyB AUTHORIZATION [tSQLt.TestClass];'); + EXEC ('CREATE SCHEMA tSQLtTestDummyC AUTHORIZATION [tSQLt.TestClass];'); + EXEC tSQLt.NewTestClass 'tSQLtTestDummyD'; + EXEC tSQLt.NewTestClass 'tSQLtTestDummyE'; + + EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; + + SELECT Name + INTO #Actual + FROM tSQLt.TestClasses; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO diff --git a/TestUtilTests/tSQLt_testutil_test_SA.class.sql b/TestUtilTests/tSQLt_testutil_test_SA.class.sql new file mode 100644 index 000000000..3c572a37d --- /dev/null +++ b/TestUtilTests/tSQLt_testutil_test_SA.class.sql @@ -0,0 +1,340 @@ +GO +EXEC tSQLt.NewTestClass 'tSQLt_testutil_test_SA'; +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable] +AS +BEGIN + EXEC tSQLt_testutil.PrepMultiRunLogTable + EXEC tSQLt.FakeTable @TableName = 'tSQLt_testutil.MultiRunLog', @Identity = 1, @ComputedColumns = 0, @Defaults = 0; +END; +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test BuildTestLog creates table with correct columns] +AS +BEGIN + DECLARE @TableName NVARCHAR(MAX) = 'dbo.['+CAST(NEWID() AS NVARCHAR(MAX))+']'; + EXEC tSQLt_testutil.CreateBuildLog @TableName = @TableName; + + CREATE TABLE tSQLt_testutil_test_SA.[testtable for comparison Expected] + ( + [id] [int] NOT NULL IDENTITY(1, 1), + [Success] [int] NULL, + [Skipped] [int] NULL, + [Failure] [int] NULL, + [Error] [int] NULL, + [TestCaseSet] NVARCHAR(MAX) NULL, + [RunGroup] NVARCHAR(MAX) NULL, + [DatabaseName] NVARCHAR(MAX) NULL, + [Version] VARCHAR(14) NULL, + [ClrVersion] NVARCHAR(4000) NULL, + [ClrSigningKey] VARBINARY(8000) NULL, + [InstalledOnSqlVersion] NUMERIC(10, 2) NULL, + [SqlVersion] NUMERIC(10, 2) NULL, + [SqlBuild] NUMERIC(10, 2) NULL, + [SqlEdition] NVARCHAR(128) NULL, + [HostPlatform] NVARCHAR(256) NULL + ); + + EXEC('SELECT TOP(0) * INTO tSQLt_testutil_test_SA.[testtable for comparison Actual] FROM '+@TableName+';') + + EXEC tSQLt.AssertEqualsTableSchema @Expected='tSQLt_testutil_test_SA.[testtable for comparison Expected]', @Actual = 'tSQLt_testutil_test_SA.[testtable for comparison Actual]'; +END; +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test StoreBuildLog transfers data from MultiRunLog and values RunGroup, DatabaseName] +AS +BEGIN + DECLARE @TableName NVARCHAR(MAX) = 'dbo.['+CAST(NEWID() AS NVARCHAR(MAX))+']'; + EXEC tSQLt_testutil.CreateBuildLog @TableName = @TableName; + + EXEC tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + SET IDENTITY_INSERT tSQLt_testutil.MultiRunLog ON; + INSERT INTO tSQLt_testutil.MultiRunLog(id,Success,Skipped,Failure,Error,TestCaseSet) + VALUES (101,123,456,678,101112,'testcaseset-101'),(102,98,76,54,32,'testcaseset-102'),(103,398,376,354,332,'testcaseset-103'); + + EXEC tSQLt_testutil.StoreBuildLog @TableName = @TableName, @RunGroup = 'SomeRunGroup'; + + SELECT ROW_NUMBER()OVER(ORDER BY MRL.id) AS id, + MRL.Success, + MRL.Skipped, + MRL.Failure, + MRL.Error, + MRL.TestCaseSet, + 'SomeRunGroup' RunGroup, + DB_NAME() DatabaseName + INTO tSQLt_testutil_test_SA.[testtable for comparison Expected] + FROM tSQLt_testutil.MultiRunLog AS MRL; + + EXEC(' SELECT ROW_NUMBER()OVER(ORDER BY MRL.id) AS id, + MRL.Success, + MRL.Skipped, + MRL.Failure, + MRL.Error, + MRL.TestCaseSet, + MRL.RunGroup, + MRL.DatabaseName + INTO tSQLt_testutil_test_SA.[testtable for comparison Actual] + FROM '+@TableName+' AS MRL;') + + EXEC tSQLt.AssertEqualsTable 'tSQLt_testutil_test_SA.[testtable for comparison Expected]','tSQLt_testutil_test_SA.[testtable for comparison Actual]'; + +END; +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test StoreBuildLog creates row if MultiRunLog is empty] +AS +BEGIN + DECLARE @TableName NVARCHAR(MAX) = 'dbo.['+CAST(NEWID() AS NVARCHAR(MAX))+']'; + EXEC tSQLt_testutil.CreateBuildLog @TableName = @TableName; + + EXEC tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + + EXEC tSQLt_testutil.StoreBuildLog @TableName = @TableName, @RunGroup = 'AnotherRunGroup'; + + SELECT 1 id, + NULL Success, + NULL Skipped, + NULL Failure, + NULL Error, + NULL TestCaseSet, + 'AnotherRunGroup' RunGroup, + DB_NAME() DatabaseName + INTO tSQLt_testutil_test_SA.[testtable for comparison Expected]; + + EXEC(' SELECT ROW_NUMBER()OVER(ORDER BY MRL.id) AS id, + MRL.Success, + MRL.Skipped, + MRL.Failure, + MRL.Error, + MRL.TestCaseSet, + MRL.RunGroup, + MRL.DatabaseName + INTO tSQLt_testutil_test_SA.[testtable for comparison Actual] + FROM '+@TableName+' AS MRL;') + + EXEC tSQLt.AssertEqualsTable 'tSQLt_testutil_test_SA.[testtable for comparison Expected]','tSQLt_testutil_test_SA.[testtable for comparison Actual]'; + +END; +GO +------------------------------------------------------------------------- +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog throws error if a test error exists in the log] +AS +BEGIN + EXEC tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Error)VALUES(1); + + EXEC tSQLt_testutil.CreateBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + EXEC tSQLt_testutil.StoreBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]',@RunGroup='ATest'; + + SELECT TOP(0)MRL.* + INTO #ignore + FROM tSQLt_testutil_test_SA.[Temp BuildLog Table] X LEFT JOIN tSQLt_testutil_test_SA.[Temp BuildLog Table] AS MRL ON 0=1; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt execution with failures or errors detected.', @ExpectedSeverity = 16, @ExpectedState = 10; + + INSERT INTO #ignore + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog throws error if a test failure exists in the log] +AS +BEGIN + EXEC tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Failure)VALUES(1); + + EXEC tSQLt_testutil.CreateBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + EXEC tSQLt_testutil.StoreBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]',@RunGroup='ATest'; + + SELECT TOP(0)MRL.* + INTO #ignore + FROM tSQLt_testutil_test_SA.[Temp BuildLog Table] X LEFT JOIN tSQLt_testutil_test_SA.[Temp BuildLog Table] AS MRL ON 0=1; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt execution with failures or errors detected.', @ExpectedSeverity = 16, @ExpectedState = 10; + + INSERT INTO #ignore + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog throws no error if all tests in the log succeeded] +AS +BEGIN + EXEC tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Success)VALUES(1); + + EXEC tSQLt_testutil.CreateBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + EXEC tSQLt_testutil.StoreBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]',@RunGroup='ATest'; + + SELECT TOP(0)MRL.* + INTO #ignore + FROM tSQLt_testutil_test_SA.[Temp BuildLog Table] X LEFT JOIN tSQLt_testutil_test_SA.[Temp BuildLog Table] AS MRL ON 0=1; + + EXEC tSQLt.ExpectNoException; + + INSERT INTO #ignore + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog can be called twice] +AS +BEGIN + EXEC tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Success)VALUES(1); + + EXEC tSQLt_testutil.CreateBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + EXEC tSQLt_testutil.StoreBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]',@RunGroup='ATest'; + + SELECT TOP(0)MRL.* + INTO #ignore + FROM tSQLt_testutil_test_SA.[Temp BuildLog Table] X LEFT JOIN tSQLt_testutil_test_SA.[Temp BuildLog Table] AS MRL ON 0=1; + + EXEC tSQLt.ExpectNoException; + + INSERT INTO #ignore + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + + INSERT INTO #ignore + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + +END +GO + +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog Private_Print is called with the result of TableToText] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.TableToText', @CommandToExecute = 'SET @txt='''';'; + + EXEC tSQLt_testutil.CreateBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + + IF NOT EXISTS(SELECT 1 FROM tSQLt.Private_Print_SpyProcedureLog WHERE Message = '') + BEGIN + EXEC tSQLt.Fail 'tSQLt.Private_Print was not called with the output of tSQLt.TableToText'; + END; + +END; +GO + +--TODO: This really needs an "undo" on the tSQLt.SpyProcedure, but we don't have time for it. +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog prints contents of Log] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.TableToText'; + + EXEC tSQLt_testutil.CreateBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + INSERT INTO tSQLt_testutil_test_SA.[Temp BuildLog Table](id, Success, Skipped, Failure, Error, TestCaseSet, RunGroup, DatabaseName) + VALUES + (101,17,9,1,3,'row 1','RG1','DB1'),(102,4,6,7,5,'row 2','RG2','DB2'); + + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + + DECLARE @TableName NVARCHAR(MAX) = (SELECT TableName FROM tSQLt.TableToText_SpyProcedureLog); + + SELECT CAST (MRL.id AS NVARCHAR(MAX)) AS id, + CAST (MRL.Success AS NVARCHAR(MAX)) AS Success, + CAST (MRL.Skipped AS NVARCHAR(MAX)) AS Skipped, + CAST (MRL.Failure AS NVARCHAR(MAX)) AS Failure, + CAST (MRL.Error AS NVARCHAR(MAX)) AS Error, + CAST (MRL.TestCaseSet AS NVARCHAR(MAX)) AS TestCaseSet, + CAST (RunGroup AS NVARCHAR(MAX)) AS RunGroup, + CAST (DatabaseName AS NVARCHAR(MAX)) AS DatabaseName + INTO tSQLt_testutil_test_SA.[testtable for comparison Expected] + FROM tSQLt_testutil_test_SA.[Temp BuildLog Table] AS MRL + + EXEC('SELECT * INTO tSQLt_testutil_test_SA.[testtable for comparison Hold] FROM ' + @TableName + ';'); + + SELECT LTRIM(RTRIM(CAST (MRL.id AS NVARCHAR(MAX)))) AS id, + LTRIM(RTRIM(CAST (MRL.Success AS NVARCHAR(MAX)))) AS Success, + LTRIM(RTRIM(CAST (MRL.Skipped AS NVARCHAR(MAX)))) AS Skipped, + LTRIM(RTRIM(CAST (MRL.Failure AS NVARCHAR(MAX)))) AS Failure, + LTRIM(RTRIM(CAST (MRL.Error AS NVARCHAR(MAX)))) AS Error, + LTRIM(RTRIM(CAST (MRL.TestCaseSet AS NVARCHAR(MAX)))) AS TestCaseSet, + LTRIM(RTRIM(CAST (RunGroup AS NVARCHAR(MAX)))) AS RunGroup, + LTRIM(RTRIM(CAST (DatabaseName AS NVARCHAR(MAX)))) AS DatabaseName + INTO tSQLt_testutil_test_SA.[testtable for comparison Actual] + FROM tSQLt_testutil_test_SA.[testtable for comparison Hold] AS MRL + + + EXEC tSQLt.AssertEqualsTable 'tSQLt_testutil_test_SA.[testtable for comparison Expected]','tSQLt_testutil_test_SA.[testtable for comparison Actual]'; +END +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog throws error if the log is empty] +AS +BEGIN + EXEC tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + + EXEC tSQLt_testutil.CreateBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + + SELECT TOP(0)MRL.* + INTO #ignore + FROM tSQLt_testutil_test_SA.[Temp BuildLog Table] X LEFT JOIN tSQLt_testutil_test_SA.[Temp BuildLog Table] AS MRL ON 0=1; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'BuildLog is empty.', @ExpectedSeverity = 16, @ExpectedState = 10; + + INSERT INTO #ignore + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog throws error if the log contains empty run] +AS +BEGIN + EXEC tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Success,Skipped,Failure,Error,TestCaseSet) + VALUES(0,0,0,0,'some run'); + + EXEC tSQLt_testutil.CreateBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + EXEC tSQLt_testutil.StoreBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]',@RunGroup='ATest'; + + SELECT TOP(0)MRL.* + INTO #ignore + FROM tSQLt_testutil_test_SA.[Temp BuildLog Table] X LEFT JOIN tSQLt_testutil_test_SA.[Temp BuildLog Table] AS MRL ON 0=1; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'BuildLog contains Run without tests.', @ExpectedSeverity = 16, @ExpectedState = 10; + + INSERT INTO #ignore + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[ptest CheckBuildLog doesn't throw error if] + @success INT = 0, + @skipped INT = 0, + @failed INT = 0, + @errored INT = 0 +AS +BEGIN + EXEC tSQLt_testutil_test_SA.[Create and fake tSQLt_testutil.PrepMultiRunLogTable]; + INSERT INTO tSQLt_testutil.MultiRunLog(Success,Skipped,Failure,Error) + VALUES(@success,@skipped,@failed,@errored); + + EXEC tSQLt_testutil.CreateBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + EXEC tSQLt_testutil.StoreBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]',@RunGroup='ATest'; + + SELECT TOP(0)MRL.* + INTO #Actual + FROM tSQLt_testutil_test_SA.[Temp BuildLog Table] X LEFT JOIN tSQLt_testutil_test_SA.[Temp BuildLog Table] AS MRL ON 0=1; + + EXEC tSQLt.ExpectNoException; + + INSERT INTO #Actual + EXEC tSQLt_testutil.CheckBuildLog @TableName = 'tSQLt_testutil_test_SA.[Temp BuildLog Table]'; + +END +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog doesn't throw error if a success exists] +AS +BEGIN + EXEC tSQLt_testutil_test_SA.[ptest CheckBuildLog doesn't throw error if] @success=1; +END +GO +CREATE PROCEDURE tSQLt_testutil_test_SA.[test CheckBuildLog doesn't throw error if a skip exists] +AS +BEGIN + EXEC tSQLt_testutil_test_SA.[ptest CheckBuildLog doesn't throw error if] @skipped=1; +END +GO diff --git a/Tests.2008/AssertEqualsTableTests_2008.class.sql b/Tests.2008/AssertEqualsTableTests_2008.class.sql deleted file mode 100644 index 803f86bf1..000000000 --- a/Tests.2008/AssertEqualsTableTests_2008.class.sql +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright 2011 tSQLt - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -EXEC tSQLt.NewTestClass 'AssertEqualsTableTests_2008'; -GO -CREATE PROCEDURE AssertEqualsTableTests_2008.[test can handle 2008 date data types] -AS -BEGIN - EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'DATE', '''2012-01-01'',''2012-06-19'',''2012-10-25'''; - EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'TIME', '''10:10:10'',''11:11:11'',''12:12:12'''; - EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'DATETIMEOFFSET', '''2012-01-01 10:10:10.101010 +10:10'',''2012-06-19 11:11:11.111111 +11:11'',''2012-10-25 12:12:12.121212 -12:12'''; - EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'DATETIME2', '''2012-01-01 10:10:10.101010'',''2012-06-19 11:11:11.111111'',''2012-10-25 12:12:12.121212'''; -END; -GO -CREATE PROCEDURE AssertEqualsTableTests_2008.[test can handle hierarchyid data type] -AS -BEGIN - EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'HIERARCHYID', '''/10/'',''/11/'',''/12/'''; -END; -GO - -CREATE PROCEDURE AssertEqualsTableTests_2008.[test all unsupported 2008 data types] -AS -BEGIN - EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can NOT handle a datatype] 'GEOMETRY', 'geometry::STPointFromText(''POINT (10 10)'', 0),geometry::STPointFromText(''POINT (11 11)'', 0),geometry::STPointFromText(''POINT (12 12)'', 0)'; - EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can NOT handle a datatype] 'GEOGRAPHY', 'geography::STGeomFromText(''LINESTRING(-10.10 10.10, -50.10 50.10)'', 4326),geography::STGeomFromText(''LINESTRING(-11.11 11.11, -50.11 50.11)'', 4326),geography::STGeomFromText(''LINESTRING(-12.12 12.12, -50.12 50.12)'', 4326)'; -END; -GO - diff --git a/Tests.2008/FakeFunctionTests_2008.class.sql b/Tests.2008/FakeFunctionTests_2008.class.sql deleted file mode 100644 index 0765660e2..000000000 --- a/Tests.2008/FakeFunctionTests_2008.class.sql +++ /dev/null @@ -1,22 +0,0 @@ -EXEC tSQLt.NewTestClass 'FakeFunctionTests_2008'; -GO -CREATE TYPE FakeFunctionTests_2008.TableType1001 AS TABLE(SomeInt INT); -GO -CREATE PROCEDURE FakeFunctionTests_2008.[test can fake funktion with table-type parameter] -AS -BEGIN - EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@p1 int, @p2 FakeFunctionTests_2008.TableType1001 READONLY, @p3 INT) RETURNS INT AS BEGIN RETURN 0; END;'); - EXEC('CREATE FUNCTION FakeFunctionTests.Fake(@p1 INT,@p2 FakeFunctionTests_2008.TableType1001 READONLY,@p3 INT) RETURNS INT AS BEGIN RETURN (SELECT SUM(SomeInt)+@p1+@p3 FROM @p2); END;'); - - EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeFunctionName = 'FakeFunctionTests.Fake'; - - DECLARE @Actual INT; - DECLARE @TableParameter FakeFunctionTests_2008.TableType1001; - INSERT INTO @TableParameter(SomeInt)VALUES(10); - INSERT INTO @TableParameter(SomeInt)VALUES(202); - INSERT INTO @TableParameter(SomeInt)VALUES(3303); - - SET @Actual = FakeFunctionTests.AFunction(10000,@TableParameter,220000); - EXEC tSQLt.AssertEqualsString @Expected = 233515, @Actual = @Actual; -END; -GO diff --git a/Tests.2008/Private_GetFullTypeNameTests_2008.class.sql b/Tests.2008/Private_GetFullTypeNameTests_2008.class.sql deleted file mode 100644 index e3ac24382..000000000 --- a/Tests.2008/Private_GetFullTypeNameTests_2008.class.sql +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2011 tSQLt - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -EXEC tSQLt.NewTestClass 'Private_GetFullTypeNameTests_2008'; -GO - -CREATE PROC Private_GetFullTypeNameTests_2008.[test Private_GetFullTypeName should handle all 2008 compatible data types] -AS -BEGIN - - CREATE TABLE dbo.Parms( - ColumnName NVARCHAR(MAX), - ColumnType NVARCHAR(MAX) - ); - - INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.date]', '[sys].[date]'); - INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.datetime2]', '[sys].[datetime2]'); - INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.datetimeoffset]', '[sys].[datetimeoffset]'); - INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.geography]', '[sys].[geography]'); - INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.geometry]', '[sys].[geometry]'); - INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.hierarchyid]', '[sys].[hierarchyid]'); - INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.time]', '[sys].[time]'); - - DECLARE @Cmd NVARCHAR(MAX); - SET @Cmd = STUFF(( - SELECT ', ' + ColumnName + ' ' + ColumnType - FROM dbo.Parms - FOR XML PATH(''), TYPE - ).value('.','NVARCHAR(MAX)'),1,2,''); - SET @Cmd = 'CREATE TABLE dbo.tst1(' + @Cmd + ');'; - - IF OBJECT_ID('dbo.tst1') IS NOT NULL DROP TABLE dbo.tst1; - EXEC(@Cmd); - - SELECT QUOTENAME(c.name) ColumnName, t.TypeName AS ColumnType - INTO #Actual - FROM sys.columns c - CROSS APPLY tSQLt.Private_GetFullTypeName(c.user_type_id,c.max_length, c.precision, c.scale, c.collation_name) t - WHERE c.object_id = OBJECT_ID('dbo.tst1'); - - EXEC tSQLt.AssertEqualsTable 'dbo.Parms','#Actual'; -END; -GO diff --git a/Tests.2008/Private_RemoveSchemBoundReferencesTests_2008.class.sql b/Tests.2008/Private_RemoveSchemBoundReferencesTests_2008.class.sql deleted file mode 100644 index 783d4a9a2..000000000 --- a/Tests.2008/Private_RemoveSchemBoundReferencesTests_2008.class.sql +++ /dev/null @@ -1,92 +0,0 @@ -EXEC tSQLt.NewTestClass 'Private_RemoveSchemBoundReferencesTests_2008'; -GO -CREATE PROCEDURE Private_RemoveSchemBoundReferencesTests_2008.[test calls RemoveSchemaBinding for single view] -AS -BEGIN - CREATE TABLE Private_RemoveSchemBoundReferencesTests_2008.T1(C1 INT); - EXEC('CREATE VIEW Private_RemoveSchemBoundReferencesTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemBoundReferencesTests_2008.T1 AS T;'); - - EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RemoveSchemaBinding'; - - DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.T1'); - EXEC tSQLt.Private_RemoveSchemaBoundReferences @object_id = @object_id; - - SELECT object_id - INTO #Actual - FROM tSQLt.Private_RemoveSchemaBinding_SpyProcedureLog; - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - INSERT INTO #Expected - VALUES(OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.V1')); - - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; - -END; -GO -CREATE PROCEDURE Private_RemoveSchemBoundReferencesTests_2008.[test calls RemoveSchemaBinding for multiple views] -AS -BEGIN - CREATE TABLE Private_RemoveSchemBoundReferencesTests_2008.T1(C1 INT); - EXEC('CREATE VIEW Private_RemoveSchemBoundReferencesTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemBoundReferencesTests_2008.T1 AS T;'); - EXEC('CREATE VIEW Private_RemoveSchemBoundReferencesTests_2008.V2 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemBoundReferencesTests_2008.T1 AS T;'); - EXEC('CREATE VIEW Private_RemoveSchemBoundReferencesTests_2008.V3 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemBoundReferencesTests_2008.T1 AS T;'); - - EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RemoveSchemaBinding'; - - DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.T1'); - EXEC tSQLt.Private_RemoveSchemaBoundReferences @object_id = @object_id; - - SELECT object_id - INTO #Actual - FROM tSQLt.Private_RemoveSchemaBinding_SpyProcedureLog; - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - INSERT INTO #Expected - VALUES(OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.V1')); - INSERT INTO #Expected - VALUES(OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.V2')); - INSERT INTO #Expected - VALUES(OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.V3')); - - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; - -END; -GO -CREATE PROCEDURE Private_RemoveSchemBoundReferencesTests_2008.[test calls RemoveSchemaBinding for multiple recursive views] -AS -BEGIN - CREATE TABLE Private_RemoveSchemBoundReferencesTests_2008.T1(C1 INT); - EXEC('CREATE VIEW Private_RemoveSchemBoundReferencesTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemBoundReferencesTests_2008.T1 AS T;'); - EXEC('CREATE VIEW Private_RemoveSchemBoundReferencesTests_2008.V2 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemBoundReferencesTests_2008.V1 AS T;'); - EXEC('CREATE VIEW Private_RemoveSchemBoundReferencesTests_2008.V3 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemBoundReferencesTests_2008.V2 AS T;'); - - EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RemoveSchemaBinding'; - - DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.T1'); - EXEC tSQLt.Private_RemoveSchemaBoundReferences @object_id = @object_id; - - SELECT _id_*1 call_order,object_id - INTO #Actual - FROM tSQLt.Private_RemoveSchemaBinding_SpyProcedureLog; - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - INSERT INTO #Expected - VALUES(1, OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.V3')); - INSERT INTO #Expected - VALUES(2, OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.V2')); - INSERT INTO #Expected - VALUES(3, OBJECT_ID('Private_RemoveSchemBoundReferencesTests_2008.V1')); - - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; - -END; -GO diff --git a/Tests.2008/Private_RemoveSchemaBindingTests_2008.class.sql b/Tests.2008/Private_RemoveSchemaBindingTests_2008.class.sql deleted file mode 100644 index 1245d420c..000000000 --- a/Tests.2008/Private_RemoveSchemaBindingTests_2008.class.sql +++ /dev/null @@ -1,202 +0,0 @@ - ---> New Direction: Drop schema bound objects and offer option to recreate as needed. - -EXEC tSQLt.NewTestClass 'Private_RemoveSchemaBindingTests_2008'; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[Assert SB property on view is removed and view is still working] - @CreateViewStatement NVARCHAR(MAX) -AS -BEGIN - CREATE TABLE Private_RemoveSchemaBindingTests_2008.T1(C1 INT); - INSERT INTO Private_RemoveSchemaBindingTests_2008.T1(C1) - VALUES(CHECKSUM(NEWID())); - INSERT INTO Private_RemoveSchemaBindingTests_2008.T1(C1) - VALUES(CHECKSUM(NEWID())); - INSERT INTO Private_RemoveSchemaBindingTests_2008.T1(C1) - VALUES(CHECKSUM(NEWID())); - - EXEC(@CreateViewStatement); - - DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemaBindingTests_2008.V1'); - EXEC tSQLt.Private_RemoveSchemaBinding @object_id = @object_id; - - SELECT QUOTENAME(OBJECT_SCHEMA_NAME(SED.referencing_id))+'.'+QUOTENAME(OBJECT_NAME(SED.referencing_id)) AS schema_bound_object_name,SED.referencing_id,SED.referencing_class_desc - INTO #SchemaBoundObjects - FROM sys.sql_expression_dependencies AS SED - WHERE SED.is_schema_bound_reference = 1 - AND SED.referenced_id = OBJECT_ID('Private_RemoveSchemaBindingTests_2008.T1'); - - EXEC tSQLt.AssertEmptyTable @TableName = '#SchemaBoundObjects'; - - SELECT * - INTO Private_RemoveSchemaBindingTests_2008.Actual - FROM Private_RemoveSchemaBindingTests_2008.V1; - - EXEC tSQLt.AssertEqualsTable 'Private_RemoveSchemaBindingTests_2008.T1','Private_RemoveSchemaBindingTests_2008.Actual'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test SB property on view is removed and view is still working] -AS -BEGIN - EXEC Private_RemoveSchemaBindingTests_2008.[Assert SB property on view is removed and view is still working] - 'CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test does not remove second W/SB statement] -AS -BEGIN - CREATE TABLE Private_RemoveSchemaBindingTests_2008.T1(C1 INT); - EXEC('CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1,''CREATE VIEW dbo.test WITH SCHEMABINDING'' AS C2 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'); - INSERT INTO Private_RemoveSchemaBindingTests_2008.T1(C1) - VALUES(42); - - DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemaBindingTests_2008.V1'); - EXEC tSQLt.Private_RemoveSchemaBinding @object_id = @object_id; - - SELECT C1,C2 - INTO #Actual - FROM Private_RemoveSchemaBindingTests_2008.V1; - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - INSERT INTO #Expected - VALUES(42,'CREATE VIEW dbo.test WITH SCHEMABINDING'); - - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test handles leading spaces] -AS -BEGIN - EXEC Private_RemoveSchemaBindingTests_2008.[Assert SB property on view is removed and view is still working] - ' CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test handles newlines] -AS -BEGIN - EXEC Private_RemoveSchemaBindingTests_2008.[Assert SB property on view is removed and view is still working] - ' - CREATE - VIEW - Private_RemoveSchemaBindingTests_2008.V1 - WITH - SCHEMABINDING - AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test handles leading one line comments] -AS -BEGIN - EXEC Private_RemoveSchemaBindingTests_2008.[Assert SB property on view is removed and view is still working] - '-- test - CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test handles leading one line comments with CREATE VIEW statement] -AS -BEGIN - EXEC Private_RemoveSchemaBindingTests_2008.[Assert SB property on view is removed and view is still working] - '-- test: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - -- test2: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - -- - CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test handles leading multi-line comments with CREATE VIEW statement] -AS -BEGIN - EXEC Private_RemoveSchemaBindingTests_2008.[Assert SB property on view is removed and view is still working] - '/* test: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - -- test2: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - */CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test handles leading double nested multi-line comments] -AS -BEGIN - EXEC Private_RemoveSchemaBindingTests_2008.[Assert SB property on view is removed and view is still working] - '/* test: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - /* test: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - */ test: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - */CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test handles leading nested multi-line comments and other niceties with CREATE VIEW statement] -AS -BEGIN - EXEC Private_RemoveSchemaBindingTests_2008.[Assert SB property on view is removed and view is still working] - '/* /test: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - /* /test: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - /* /test: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - -- *test2: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - */-- *test2: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - */-- *test2: CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS - */--/* - CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'; -END; -GO -CREATE PROCEDURE Private_RemoveSchemaBindingTests_2008.[test works on indexed view] -AS -BEGIN - CREATE TABLE Private_RemoveSchemaBindingTests_2008.T1(C1 INT); - EXEC('CREATE VIEW Private_RemoveSchemaBindingTests_2008.V1 WITH SCHEMABINDING AS SELECT T.C1,''A Constant'' AS C2 FROM Private_RemoveSchemaBindingTests_2008.T1 AS T;'); - EXEC('CREATE UNIQUE CLUSTERED INDEX [CI:V1] ON Private_RemoveSchemaBindingTests_2008.V1(C1);'); - EXEC('CREATE NONCLUSTERED INDEX [NCI:V1] ON Private_RemoveSchemaBindingTests_2008.V1(C2);'); - INSERT INTO Private_RemoveSchemaBindingTests_2008.T1(C1) - VALUES(42); - - DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemaBindingTests_2008.V1'); - EXEC tSQLt.Private_RemoveSchemaBinding @object_id = @object_id; - - SELECT C1,C2 - INTO #Actual - FROM Private_RemoveSchemaBindingTests_2008.V1; - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - INSERT INTO #Expected - VALUES(42,'A Constant'); - - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; -END; -GO - - -/* --- Comments before create view including --- superflous whitespace (CREATE VIEW as well as WITH SCHEMABINDING) --- indexed views --- computed columns --- indexed computed columns --- other object types: (see remarks) http://msdn.microsoft.com/en-us/library/bb677315.aspx - - - SELECT * FROM sys.sql_expression_dependencies AS SED - JOIN sys.sql_modules AS SM - ON SED.referencing_id = SM.object_id - WHERE SED.referenced_id = OBJECT_ID('Private_RemoveSchemaBindingTests_2008.T1'); - - --- /* --- PRINT 1; --- --/* --- PRINT 1; --- /* --- PRINT 1; --- --*/ --- PRINT 1; --- --*/ --- PRINT 1; --- --*/ --- PRINT 1; - - - - - ---*/ \ No newline at end of file diff --git a/Tests.2008/Private_ScriptIndexTests_2008.class.sql b/Tests.2008/Private_ScriptIndexTests_2008.class.sql deleted file mode 100644 index a8888e41e..000000000 --- a/Tests.2008/Private_ScriptIndexTests_2008.class.sql +++ /dev/null @@ -1,23 +0,0 @@ -EXEC tSQLt.NewTestClass 'Private_ScriptIndexTests_2008'; -GO -CREATE PROCEDURE Private_ScriptIndexTests_2008.[test handles filter] -AS -BEGIN - EXEC Private_ScriptIndexTests.[assert index is scripted correctly] - @index_create_cmd = 'CREATE UNIQUE NONCLUSTERED INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1]([C1]ASC,[C3]DESC)INCLUDE([C4],[C2])WHERE([C1]=(3));'; -END; -GO -CREATE PROCEDURE Private_ScriptIndexTests.[test handles hypothetical index] -AS -BEGIN - EXEC Private_ScriptIndexTests.[assert index is scripted correctly] - @index_create_cmd = 'CREATE UNIQUE NONCLUSTERED INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1]([C1]ASC,[C3]DESC)INCLUDE([C4],[C2])WITH(STATISTICS_ONLY = -1);'; -END; -GO -CREATE PROCEDURE Private_ScriptIndexTests_2008.[test handles hypothetical filtered index] -AS -BEGIN - EXEC Private_ScriptIndexTests.[assert index is scripted correctly] - @index_create_cmd = 'CREATE UNIQUE NONCLUSTERED INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1]([C1]ASC,[C3]DESC)INCLUDE([C4],[C2])WHERE([C1]=(3))WITH(STATISTICS_ONLY = -1);'; -END; -GO diff --git a/Tests.2008/Private_SqlVariantFormatterTests_2008.class.sql b/Tests.2008/Private_SqlVariantFormatterTests_2008.class.sql deleted file mode 100644 index b60f362d0..000000000 --- a/Tests.2008/Private_SqlVariantFormatterTests_2008.class.sql +++ /dev/null @@ -1,43 +0,0 @@ -EXEC tSQLt.NewTestClass 'Private_SqlVariantFormatterTests_2008'; -GO -CREATE PROC Private_SqlVariantFormatterTests_2008.[test formats new 2008 data types] -AS -BEGIN - CREATE TABLE #Input( - [DATE] DATE, - [DATETIME2] DATETIME2, - [DATETIMEOFFSET] DATETIMEOFFSET, - [TIME] TIME - ); - INSERT INTO #Input - SELECT - '2013-04-05' AS [DATE], - '2013-04-05T06:07:08.9876543' AS [DATETIME2], - '2013-04-05T06:07:08.9876543+02:01' AS [DATETIMEOFFSET], - '06:07:08.9876543'AS [TIME]; - - CREATE TABLE #Actual( - [DATE] NVARCHAR(MAX), - [DATETIME2] NVARCHAR(MAX), - [DATETIMEOFFSET] NVARCHAR(MAX), - [TIME] NVARCHAR(MAX) - ); - INSERT INTO #Actual - SELECT - tSQLt.Private_SqlVariantFormatter([DATE]) AS [DATE], - tSQLt.Private_SqlVariantFormatter([DATETIME2]) AS [DATETIME2], - tSQLt.Private_SqlVariantFormatter([DATETIMEOFFSET]) AS [DATETIMEOFFSET], - tSQLt.Private_SqlVariantFormatter([TIME]) AS [TIME] - FROM #Input; - - SELECT TOP(0) * INTO #Expected FROM #Actual; - INSERT INTO #Expected - SELECT - '2013-04-05' AS [DATE], - '2013-04-05T06:07:08.9876543' AS [DATETIME2], - '2013-04-05T06:07:08.9876543+02:01' AS [DATETIMEOFFSET], - '06:07:08.9876543' AS [TIME]; - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; -END; -GO diff --git a/Tests.2008/Run_Methods_Tests_2008.class.sql b/Tests.2008/Run_Methods_Tests_2008.class.sql deleted file mode 100644 index 31646f8cb..000000000 --- a/Tests.2008/Run_Methods_Tests_2008.class.sql +++ /dev/null @@ -1,251 +0,0 @@ -EXEC tSQLt.NewTestClass 'Run_Methods_Tests_2008'; -GO ---Valid JUnit XML Schema ---Source:https://raw.githubusercontent.com/windyroad/JUnit-Schema/master/JUnit.xsd -DECLARE @cmd NVARCHAR(MAX);SET @cmd = -' - - - - JUnit test result schema for the Apache Ant JUnit and JUnitReport tasks -Copyright © 2011, Windy Road Technology Pty. Limited -The Apache Ant JUnit XML Schema is distributed under the terms of the Apache License Version 2.0 http://www.apache.org/licenses/ -Permission to waive conditions of this license may be requested from Windy Road Support (http://windyroad.org/support). - - - - - - - - - - Contains an aggregation of testsuite results - - - - - - - - - - Derived from testsuite/@name in the non-aggregated documents - - - - - Starts at ''0'' for the first testsuite and is incremented by 1 for each following testsuite - - - - - - - - - - - - Contains the results of exexuting a testsuite - - - - - Properties (e.g., environment settings) set during test execution - - - - - - - - - - - - - - - - - - - - - - - - Indicates that the test errored. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test. Contains as a text node relevant data for the error, e.g., a stack trace - - - - - - - The error message. e.g., if a java exception is thrown, the return value of getMessage() - - - - - The type of error that occured. e.g., if a java execption is thrown the full class name of the exception. - - - - - - - - - Indicates that the test failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals. Contains as a text node relevant data for the failure, e.g., a stack trace - - - - - - - The message specified in the assert - - - - - The type of the assert. - - - - - - - - - - Name of the test method - - - - - Full class name for the class the test method is in. - - - - - Time taken (in seconds) to execute the test - - - - - - - Data that was written to standard out while the test was executed - - - - - - - - - - Data that was written to standard error while the test was executed - - - - - - - - - - - Full class name of the test for non-aggregated testsuite documents. Class name without the package for aggregated testsuites documents - - - - - - - - - - when the test was executed. Timezone may not be specified. - - - - - Host on which the tests were executed. ''localhost'' should be used if the hostname cannot be determined. - - - - - - - - - - The total number of tests in the suite - - - - - The total number of tests in the suite that failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals - - - - - The total number of tests in the suite that errorrd. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test. - - - - - Time taken (in seconds) to execute the tests in the suite - - - - - - - - -'; -SET @cmd = 'CREATE XML SCHEMA COLLECTION Run_Methods_Tests_2008.ValidJUnitXML AS '''+REPLACE(REPLACE(@cmd,'''',''''''),'©','(c)')+''';'; ---EXEC(@cmd); -EXEC tSQLt.CaptureOutput @cmd; -GO -CREATE PROC Run_Methods_Tests_2008.[test XmlResultFormatter returns XML that validates against the JUnit specification] -AS -BEGIN - EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; - EXEC tSQLt.SpyProcedure 'tSQLt.Private_PrintXML'; - - DELETE FROM tSQLt.TestResult; - INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime) - VALUES ('MyTestClass1', 'testA', 'Failure', '2015-07-24T00:00:01.000', '2015-07-24T00:00:01.138'); - INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime) - VALUES ('MyTestClass1', 'testB', 'Success', '2015-07-24T00:00:02.000', '2015-07-24T00:00:02.633'); - INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime) - VALUES ('MyTestClass2', 'testC', 'Failure', '2015-07-24T00:00:01.111', '2015-07-24T20:31:24.758'); - INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime) - VALUES ('MyTestClass2', 'testD', 'Error', '2015-07-24T00:00:00.667', '2015-07-24T00:00:01.055'); - - EXEC tSQLt.XmlResultFormatter; - - EXEC tSQLt.ExpectNoException; - DECLARE @XML XML(Run_Methods_Tests_2008.ValidJUnitXML); - SELECT @XML = CAST(Message AS XML) FROM tSQLt.Private_PrintXML_SpyProcedureLog; - -END; -GO -CREATE PROCEDURE Run_Methods_Tests_2008.[test tSQLt.Private_InputBuffer does not produce output] -AS -BEGIN - DECLARE @Actual NVARCHAR(MAX);SET @Actual = ''; - - EXEC tSQLt.CaptureOutput 'DECLARE @r NVARCHAR(MAX);EXEC tSQLt.Private_InputBuffer @r OUT;'; - - SELECT @Actual = COL.OutputText FROM tSQLt.CaptureOutputLog AS COL; - - EXEC tSQLt.AssertEqualsString @Expected = NULL, @Actual = @Actual; -END -GO diff --git a/Tests.2008/SpyProcedureTests_2008.class.sql b/Tests.2008/SpyProcedureTests_2008.class.sql deleted file mode 100644 index c162f080a..000000000 --- a/Tests.2008/SpyProcedureTests_2008.class.sql +++ /dev/null @@ -1,32 +0,0 @@ -EXEC tSQLt.NewTestClass 'SpyProcedureTests_2008'; -GO -CREATE TYPE SpyProcedureTests_2008.TableType1001 AS TABLE(SomeInt INT, SomeVarChar VARCHAR(10)); -GO -CREATE PROC SpyProcedureTests_2008.[test SpyProcedure can have a table type parameter] -AS -BEGIN - EXEC('CREATE PROC SpyProcedureTests_2008.InnerProcedure @P1 SpyProcedureTests_2008.TableType1001 READONLY AS EXEC tSQLt.Fail ''InnerProcedure was executed;'''); - - EXEC tSQLt.SpyProcedure 'SpyProcedureTests_2008.InnerProcedure'; - - DECLARE @TableParameter SpyProcedureTests_2008.TableType1001; - INSERT INTO @TableParameter(SomeInt,SomeVarChar)VALUES(10,'S1'); - INSERT INTO @TableParameter(SomeInt,SomeVarChar)VALUES(202,'V2'); - INSERT INTO @TableParameter(SomeInt,SomeVarChar)VALUES(3303,'C3'); - - DECLARE @InnerProcedure VARCHAR(MAX);SET @InnerProcedure = 'SpyProcedureTests_2008.InnerProcedure' - EXEC @InnerProcedure @P1 = @TableParameter; - - SELECT RowNode.value('SomeInt[1]','INT') SomeInt,RowNode.value('SomeVarChar[1]','VARCHAR(10)') SomeVarChar - INTO #Actual - FROM SpyProcedureTests_2008.InnerProcedure_SpyProcedureLog - CROSS APPLY P1.nodes('P1/row') AS N(RowNode); - - SELECT * - INTO #Expected - FROM @TableParameter AS TP; - - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; - -END; -GO diff --git a/Tests.2008/TableToTextTests_2008.class.sql b/Tests.2008/TableToTextTests_2008.class.sql deleted file mode 100644 index a98fd4349..000000000 --- a/Tests.2008/TableToTextTests_2008.class.sql +++ /dev/null @@ -1,84 +0,0 @@ -/* - Copyright 2011 tSQLt - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -EXEC tSQLt.NewTestClass 'TableToTextTests_2008'; -GO -CREATE PROC TableToTextTests_2008.[test TableToText works for one DATETIMEOFFSET column #table] -AS -BEGIN - CREATE TABLE #DoesExist( - T DATETIMEOFFSET - ); - INSERT INTO #DoesExist (T)VALUES(CAST('2001-10-13 12:34:56.7891234 +13:24' AS DATETIMEOFFSET)); - - DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); - - EXEC tSQLt.AssertEqualsString '|T | -+----------------------------------+ -|2001-10-13 12:34:56.7891234 +13:24|', @result; -END; -GO - -CREATE PROC TableToTextTests_2008.[test TableToText works for one DATETIME2 column #table] -AS -BEGIN - CREATE TABLE #DoesExist( - T DATETIME2 - ); - INSERT INTO #DoesExist (T)VALUES(CAST('2001-10-13T12:34:56.7891234' AS DATETIME2)); - - DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); - - EXEC tSQLt.AssertEqualsString '|T | -+---------------------------+ -|2001-10-13 12:34:56.7891234|', @result; -END; -GO - -CREATE PROC TableToTextTests_2008.[test TableToText works for one TIME column #table] -AS -BEGIN - CREATE TABLE #DoesExist( - T TIME - ); - INSERT INTO #DoesExist (T)VALUES('2001-10-13T12:34:56.7871234'); - - DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); - - EXEC tSQLt.AssertEqualsString '|T | -+----------------+ -|12:34:56.7871234|', @result; -END; -GO - -CREATE PROC TableToTextTests_2008.[test TableToText works for one DATE column #table] -AS -BEGIN - CREATE TABLE #DoesExist( - T DATE - ); - INSERT INTO #DoesExist (T)VALUES('2001-10-13T12:34:56.787'); - - DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); - - EXEC tSQLt.AssertEqualsString '|T | -+----------+ -|2001-10-13|', @result; -END; -GO diff --git a/Tests.2008/Tests.2008.ssmssqlproj b/Tests.2008/Tests.2008.ssmssqlproj deleted file mode 100644 index 0a30ee59a..000000000 --- a/Tests.2008/Tests.2008.ssmssqlproj +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - 2014-11-24T18:17:50.1825875-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_Dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertEqualsTableTests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertStringInTests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - FakeFunctionTests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_GetFullTypeNameTests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_RemoveSchemaBindingTests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_RemoveSchemBoundReferencesTests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_ScriptIndexTests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_SqlVariantFormatterTests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Run_Methods_Tests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - SpyProcedureTests_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt_test_ResultSetFilter_2008.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - TableToTextTests_2008.class.sql - - - - - - - - \ No newline at end of file diff --git a/Tests.2008/tSQLt_test_ResultSetFilter_2008.class.sql b/Tests.2008/tSQLt_test_ResultSetFilter_2008.class.sql deleted file mode 100644 index 2bf6db79f..000000000 --- a/Tests.2008/tSQLt_test_ResultSetFilter_2008.class.sql +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright 2012 tSQLt - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -EXEC tSQLt.NewTestClass 'tSQLt_test_ResultSetFilter_2008'; -GO - -CREATE PROCEDURE tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype - @Value NVARCHAR(MAX), - @Datatype NVARCHAR(MAX) -AS -BEGIN - DECLARE @ExpectedStmt NVARCHAR(MAX), - @ActualStmt NVARCHAR(MAX); - - DECLARE @ActualValue NVARCHAR(MAX); - SET @ActualValue = REPLACE(@Value, '''', ''''''); - - SELECT @ExpectedStmt = 'SELECT CAST(' + @Value + ' AS ' + @Datatype + ') AS val;'; - SELECT @ActualStmt = 'EXEC tSQLt.ResultSetFilter 1, ''SELECT CAST(' + @ActualValue + ' AS ' + @Datatype + ') AS val;'''; - - EXEC tSQLt.AssertResultSetsHaveSameMetaData @ExpectedStmt, @ActualStmt; - -END -GO - -CREATE PROC tSQLt_test_ResultSetFilter_2008.[test ResultSetFilter can handle each 2008 datatype] -AS -BEGIN - EXEC tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797''', 'DATETIME2'; - EXEC tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797''', 'DATETIME2(3)'; - EXEC tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797 +01:15''', 'DATETIMEOFFSET'; - EXEC tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797 +01:15''', 'DATETIMEOFFSET(3)'; - EXEC tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797''', 'DATE'; - EXEC tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797''', 'TIME'; - - EXEC tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype 'geometry::STGeomFromText(''LINESTRING (100 100, 20 180, 180 180)'', 0)', 'geometry'; - EXEC tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype 'geography::STGeomFromText(''LINESTRING(-122.360 47.656, -122.343 47.656)'', 4326)', 'geography'; - EXEC tSQLt_test_ResultSetFilter_2008.AssertResultSetFilterCanHandleDatatype 'hierarchyid::Parse(''/1/'')', 'hierarchyid'; - -END -GO - diff --git a/Tests.2012/Private_ScriptIndexTests_2012.class.sql b/Tests.2012/Private_ScriptIndexTests_2012.class.sql deleted file mode 100644 index 276b015a8..000000000 --- a/Tests.2012/Private_ScriptIndexTests_2012.class.sql +++ /dev/null @@ -1,10 +0,0 @@ -EXEC tSQLt.NewTestClass 'Private_ScriptIndexTests_2012'; -GO -CREATE PROCEDURE Private_ScriptIndexTests_2012.[test handles nonclustered columnstore index] -AS -BEGIN - EXEC Private_ScriptIndexTests.[assert index is scripted correctly] - @index_create_cmd = 'CREATE NONCLUSTERED COLUMNSTORE INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1]([C1],[C3]);'; -END; -GO - diff --git a/Tests.2012/Tests.2012.ssmssqlproj b/Tests.2012/Tests.2012.ssmssqlproj deleted file mode 100644 index c85ab34c0..000000000 --- a/Tests.2012/Tests.2012.ssmssqlproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - 2015-01-02T21:43:55.465163-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_Dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_ScriptIndexTests_2012.class.sql - - - - - - - - \ No newline at end of file diff --git a/Tests.2014/Private_ScriptIndexTests_2014.class.sql b/Tests.2014/Private_ScriptIndexTests_2014.class.sql deleted file mode 100644 index e45e88dc1..000000000 --- a/Tests.2014/Private_ScriptIndexTests_2014.class.sql +++ /dev/null @@ -1,9 +0,0 @@ -EXEC tSQLt.NewTestClass 'Private_ScriptIndexTests_2014'; -GO -CREATE PROCEDURE Private_ScriptIndexTests_2014.[test handles clustered columnstore index] -AS -BEGIN - EXEC Private_ScriptIndexTests.[assert index is scripted correctly] - @index_create_cmd = 'CREATE CLUSTERED COLUMNSTORE INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1];'; -END; -GO diff --git a/Tests.2014/Tests.2014.ssmssqlproj b/Tests.2014/Tests.2014.ssmssqlproj deleted file mode 100644 index 24148289c..000000000 --- a/Tests.2014/Tests.2014.ssmssqlproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - 2015-01-02T21:44:13.1729336-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_Dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_ScriptIndexTests_2014.class.sql - - - - - - - - \ No newline at end of file diff --git a/Tests.EXTERNAL_ACCESS/Run_Methods_Tests_EA.class.sql b/Tests.EXTERNAL_ACCESS/Run_Methods_Tests_EA.class.sql index 0e31ea414..8a7f84baf 100644 --- a/Tests.EXTERNAL_ACCESS/Run_Methods_Tests_EA.class.sql +++ b/Tests.EXTERNAL_ACCESS/Run_Methods_Tests_EA.class.sql @@ -4,6 +4,7 @@ CREATE TABLE Run_Methods_Tests_EA.[table 4 tSQLt.Private_InputBuffer tests]( InputBuffer NVARCHAR(MAX) ); GO +--[@tSQLt:RunOnlyOnHostPlatform]('Windows') CREATE PROCEDURE Run_Methods_Tests_EA.[test tSQLt.Private_InputBuffer returns actual INPUTBUFFER] AS BEGIN diff --git a/Tests.EXTERNAL_ACCESS/Tests.EXTERNAL_ACCESS.sqlproj b/Tests.EXTERNAL_ACCESS/Tests.EXTERNAL_ACCESS.sqlproj new file mode 100644 index 000000000..0ca6a4b91 --- /dev/null +++ b/Tests.EXTERNAL_ACCESS/Tests.EXTERNAL_ACCESS.sqlproj @@ -0,0 +1,13 @@ + + + + + Tests.EXTERNAL_ACCESS + {F7261171-19E3-441E-9622-9472A4792AE9} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + 1033, CI + + + + + \ No newline at end of file diff --git a/Tests.EXTERNAL_ACCESS/Tests.EXTERNAL_ACCESS.ssmssqlproj b/Tests.EXTERNAL_ACCESS/Tests.EXTERNAL_ACCESS.ssmssqlproj deleted file mode 100644 index dacecb5ed..000000000 --- a/Tests.EXTERNAL_ACCESS/Tests.EXTERNAL_ACCESS.ssmssqlproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - 2015-12-15T21:51:23.5541257-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_Dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Run_Methods_Tests_EA.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLtclr_test.class.sql - - - - - - - - \ No newline at end of file diff --git a/Tests.EXTERNAL_ACCESS/tSQLtclr_test.class.sql b/Tests.EXTERNAL_ACCESS/tSQLtclr_test.class.sql index d438284fa..8643b0d96 100644 --- a/Tests.EXTERNAL_ACCESS/tSQLtclr_test.class.sql +++ b/Tests.EXTERNAL_ACCESS/tSQLtclr_test.class.sql @@ -15,9 +15,7 @@ */ EXEC tSQLt.NewTestClass 'tSQLtclr_tests_EA'; GO - - - +--[@tSQLt:RunOnlyOnHostPlatform]('Windows') CREATE PROC tSQLtclr_tests_EA.[test NewConnection executes a command in a new process] AS BEGIN diff --git a/Tests.EXTERNAL_ACCESS_KEY_EXISTS/EnableExternalAccessTests.class.sql b/Tests.EXTERNAL_ACCESS_KEY_EXISTS/EnableExternalAccessTests.class.sql index b188e2e84..63e4b7d54 100644 --- a/Tests.EXTERNAL_ACCESS_KEY_EXISTS/EnableExternalAccessTests.class.sql +++ b/Tests.EXTERNAL_ACCESS_KEY_EXISTS/EnableExternalAccessTests.class.sql @@ -1,5 +1,6 @@ EXEC tSQLt.NewTestClass 'EnableExternalAccessTests'; GO +--[@tSQLt:RunOnlyOnHostPlatform]('Windows') CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess sets PERMISSION_SET to EXTERNAL_ACCESS if called without parameters] AS BEGIN @@ -13,16 +14,23 @@ BEGIN EXEC tSQLt.AssertEqualsString @Expected = 'EXTERNAL_ACCESS', @Actual = @Actual; END; GO +--[@tSQLt:RunOnlyOnHostPlatform]('Windows') CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess reports meaningful error with details, if setting fails] AS BEGIN + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = SAFE; - EXEC master.tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke; - - EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'The attempt to enable tSQLt features requiring EXTERNAL_ACCESS failed: ALTER ASSEMBLY%tSQLtCLR%failed%EXTERNAL_ACCESS%'; + EXEC master.tSQLt_testutil.tSQLtTestUtil_UnsafeAssemblyAndExternalAccessRevoke; + DECLARE @ExpectedMessagePattern NVARCHAR(MAX) = + 'The attempt to enable tSQLt features requiring EXTERNAL_ACCESS failed: ALTER ASSEMBLY%tSQLtCLR%failed%'+ + CASE WHEN @ProductMajorVersion>=14 THEN 'UNSAFE ASSEMBLY' ELSE 'EXTERNAL_ACCESS' END+ + '%'; + EXEC tSQLt.ExpectException @ExpectedMessagePattern = @ExpectedMessagePattern; EXEC tSQLt.EnableExternalAccess; END; GO +--[@tSQLt:RunOnlyOnHostPlatform]('Windows') CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess sets PERMISSION_SET to SAFE if @enable = 0] AS BEGIN @@ -36,39 +44,78 @@ BEGIN EXEC tSQLt.AssertEqualsString @Expected = 'SAFE_ACCESS', @Actual = @Actual; END; GO -CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess produces no output, if @try = 1 and setting fails] +--[@tSQLt:MinSqlMajorVersion](11) +CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess produces no output, if @try = 1 and setting fails (2012++)] AS BEGIN ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = SAFE; - EXEC master.tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke; - + CREATE USER EnableExternalAccessTestsTempUser WITHOUT LOGIN; + GRANT EXECUTE ON tSQLt.EnableExternalAccess TO EnableExternalAccessTestsTempUser; + + EXEC tSQLt.CaptureOutput ' + BEGIN TRY + EXECUTE AS USER=''EnableExternalAccessTestsTempUser''; + EXEC tSQLt.EnableExternalAccess @try = 1; + REVERT; + END TRY + BEGIN CATCH + REVERT; + DECLARE @msg NVARCHAR(MAX) = ''GOTHERE:''+ERROR_MESSAGE(); + RAISERROR(@msg,16,10); + END CATCH; + '; + + SELECT OutputText + INTO #Actual + FROM tSQLt.CaptureOutputLog; + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + INSERT INTO #Expected + VALUES(NULL); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](10) +CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess produces no output, if @try = 1 and setting fails (2008,2008R2)] +AS +BEGIN + ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = SAFE; + EXEC master.tSQLt_testutil.tSQLtTestUtil_UnsafeAssemblyAndExternalAccessRevoke; + EXEC tSQLt.CaptureOutput 'EXEC tSQLt.EnableExternalAccess @try = 1;'; SELECT OutputText INTO #Actual FROM tSQLt.CaptureOutputLog; + SELECT TOP(0) * INTO #Expected FROM #Actual; + INSERT INTO #Expected VALUES(NULL); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO - +--[@tSQLt:RunOnlyOnHostPlatform]('Windows') CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess reports meaningful error if disabling fails] AS BEGIN ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = EXTERNAL_ACCESS; - DECLARE @cmd NVARCHAR(MAX); - SELECT @cmd = SM.definition FROM sys.sql_modules AS SM WHERE SM.object_id = OBJECT_ID('tSQLt.EnableExternalAccess'); + DECLARE @cmdEEA NVARCHAR(MAX); + SELECT @cmdEEA = SM.definition FROM sys.sql_modules AS SM WHERE SM.object_id = OBJECT_ID('tSQLt.EnableExternalAccess'); + DECLARE @cmdInfo NVARCHAR(MAX); + SELECT @cmdInfo = 'CREATE FUNCTION tSQLt.Info() RETURNS TABLE AS RETURN SELECT ''Windows'' HostPlatform;'; DECLARE @TranName VARCHAR(32);SET @TranName = REPLACE((CAST(NEWID() AS VARCHAR(36))),'-',''); SAVE TRAN @TranName; EXEC tSQLt.Uninstall; EXEC('CREATE SCHEMA tSQLt;'); - EXEC(@cmd); + EXEC(@cmdEEA); + EXEC(@cmdInfo); DECLARE @Actual NVARCHAR(MAX);SET @Actual = 'No error raised!'; BEGIN TRY @@ -82,21 +129,25 @@ BEGIN EXEC tSQLt.AssertLike @ExpectedPattern = 'The attempt to disable tSQLt features requiring EXTERNAL_ACCESS failed: %tSQLtCLR%', @Actual = @Actual; END; GO - -CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess retunrs -1, if @try = 1 and setting fails] +--[@tSQLt:MinSqlMajorVersion](11) +-- There's a bug in SQL Server 2008R2 that makes the EXECUTE AS statement fail randomly. Disabling it on that version for now. +CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess returns -1, if @try = 1 and setting fails] AS BEGIN ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = SAFE; - EXEC master.tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke; + CREATE USER EnableExternalAccessTestsTempUser WITHOUT LOGIN; + GRANT EXECUTE ON tSQLt.EnableExternalAccess TO EnableExternalAccessTestsTempUser; DECLARE @Actual INT; - EXEC @Actual = tSQLt.EnableExternalAccess @try = 1; + EXECUTE AS USER = 'EnableExternalAccessTestsTempUser'; + EXEC sys.sp_executesql N'EXEC @Actual = tSQLt.EnableExternalAccess @try = 1;',N'@Actual INT OUT',@Actual OUT; + REVERT EXEC tSQLt.AssertEquals -1,@Actual; END; GO - -CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess retunrs 0, if @try = 1 and setting is successful] +--[@tSQLt:RunOnlyOnHostPlatform]('Windows') +CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess returns 0, if @try = 1 and setting is successful] AS BEGIN ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = SAFE; @@ -107,5 +158,33 @@ BEGIN EXEC tSQLt.AssertEquals 0,@Actual; END; GO +CREATE FUNCTION EnableExternalAccessTests.[HostPlatform Linux]() +RETURNS TABLE +AS +RETURN SELECT '1' Version, '1' ClrVersion, NULL SqlVersion, NULL SqlBuild, 'Developer Edition (64-bit)' SqlEdition, 'Linux' HostPlatform; +GO +CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess reports meaningful error if HostPlatform is Linux] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Info', @FakeFunctionName = 'EnableExternalAccessTests.[HostPlatform Linux]'; + + DECLARE @ExpectedMessagePattern NVARCHAR(MAX) = + 'tSQLt.EnableExternalAccess is not supported on Linux.'; + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = @ExpectedMessagePattern; + EXEC tSQLt.EnableExternalAccess; +END; +GO +CREATE PROCEDURE EnableExternalAccessTests.[test tSQLt.EnableExternalAccess returns -1, if @try = 1 and HostPlatform is Linux] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Info', @FakeFunctionName = 'EnableExternalAccessTests.[HostPlatform Linux]'; + + DECLARE @Actual INT; + EXEC @Actual = tSQLt.EnableExternalAccess @try = 1; + + EXEC tSQLt.AssertEquals -1,@Actual; +END; +GO diff --git a/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Private_InitTests_EAKE.class.sql b/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Private_InitTests_EAKE.class.sql index 2b1f9c3ed..a92c369d3 100644 --- a/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Private_InitTests_EAKE.class.sql +++ b/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Private_InitTests_EAKE.class.sql @@ -1,5 +1,6 @@ EXEC tSQLt.NewTestClass 'Private_InitTests_EAKE'; GO +--[@tSQLt:RunOnlyOnHostPlatform]('Windows') CREATE PROCEDURE Private_InitTests_EAKE.[test Private_Init enables external access if possible] AS BEGIN @@ -13,49 +14,39 @@ BEGIN EXEC tSQLt.AssertEqualsString @Expected = 'EXTERNAL_ACCESS', @Actual = @Actual; END; GO -CREATE PROCEDURE Private_InitTests_EAKE.[test Private_Init does not fail if external access isn't possible] +CREATE PROCEDURE Private_InitTests_EAKE.[test Private_Init does not fail if enabling external access isn't possible] AS BEGIN - ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = SAFE; - EXEC('EXEC master.tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.EnableExternalAccess', @CommandToExecute = NULL; - EXEC tSQLt.ExpectNoException; EXEC tSQLt.Private_Init; + SELECT [try] INTO #Actual FROM tSQLt.EnableExternalAccess_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO +--[@tSQLt:MaxSqlMajorVersion](13) CREATE PROCEDURE Private_InitTests_EAKE.[test Private_Init fails if CLR cannot be accessed] AS BEGIN EXEC('ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = EXTERNAL_ACCESS;'); - EXEC('EXEC master.tSQLt_testutil.tSQLtTestUtil_ExternalAccessRevoke;'); + EXEC('EXEC master.tSQLt_testutil.tSQLtTestUtil_UnsafeAssemblyAndExternalAccessRevoke;'); EXEC tSQLt.ExpectException @ExpectedMessage = 'Cannot access CLR. Assembly might be in an invalid state. Try running EXEC tSQLt.EnableExternalAccess @enable = 0; or reinstalling tSQLt.', @ExpectedSeverity = 16, @ExpectedState = 10; EXEC tSQLt.Private_Init; -END; -GO -CREATE FUNCTION Private_InitTests_EAKE.[mismatching versions]() -RETURNS TABLE -AS -RETURN SELECT '1234' Version, '4567' ClrVersion, NULL SqlVersion, NULL SqlBuild, NULL SqlEdition; -GO -CREATE PROCEDURE Private_InitTests_EAKE.[test Private_Init fails if versions do not match] -AS -BEGIN - - EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Info', @FakeFunctionName = 'Private_InitTests_EAKE.[mismatching versions]'; - - EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt is in an invalid state. Please reinstall tSQLt.', @ExpectedSeverity = 16, @ExpectedState = 10; - EXEC tSQLt.Private_Init; - END; GO CREATE FUNCTION Private_InitTests_EAKE.[SQL Azure Edition]() RETURNS TABLE AS -RETURN SELECT '1' Version, '1' ClrVersion, NULL SqlVersion, NULL SqlBuild, 'SQL Azure' SqlEdition; +RETURN SELECT '1' Version, '1' ClrVersion, 0 SqlVersion, NULL SqlBuild, 'SQL Azure' SqlEdition, 'Windows' HostPlatform, 0 InstalledOnSqlVersion; GO CREATE PROCEDURE Private_InitTests_EAKE.[test does not call EnableExternalAccess if Edition='SQL Azure'] AS @@ -70,3 +61,8 @@ BEGIN END; GO +CREATE FUNCTION Private_InitTests_EAKE.[HostPlatform Linux]() +RETURNS TABLE +AS +RETURN SELECT '1' Version, '1' ClrVersion, NULL SqlVersion, NULL SqlBuild, 'Developer Edition (64-bit)' SqlEdition, 'Linux' HostPlatform; +GO diff --git a/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Tests.EXTERNAL_ACCESS_KEY_EXISTS.sqlproj b/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Tests.EXTERNAL_ACCESS_KEY_EXISTS.sqlproj new file mode 100644 index 000000000..340b83208 --- /dev/null +++ b/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Tests.EXTERNAL_ACCESS_KEY_EXISTS.sqlproj @@ -0,0 +1,13 @@ + + + + + Tests.EXTERNAL_ACCESS_KEY_EXISTS + {AB42824F-D996-4DCA-89B1-A50D9EBE95A4} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + 1033, CI + + + + + \ No newline at end of file diff --git a/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Tests.EXTERNAL_ACCESS_KEY_EXISTS.ssmssqlproj b/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Tests.EXTERNAL_ACCESS_KEY_EXISTS.ssmssqlproj deleted file mode 100644 index 817b0047c..000000000 --- a/Tests.EXTERNAL_ACCESS_KEY_EXISTS/Tests.EXTERNAL_ACCESS_KEY_EXISTS.ssmssqlproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - 2016-01-22T15:17:43.6518823-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - EnableExternalAccessTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_InitTests_EAKE.class.sql - - - - - - - - \ No newline at end of file diff --git a/Tests.SA/InstallAssemblyKeyTests.class.sql b/Tests.SA/InstallAssemblyKeyTests.class.sql new file mode 100644 index 000000000..782d1e0b3 --- /dev/null +++ b/Tests.SA/InstallAssemblyKeyTests.class.sql @@ -0,0 +1,515 @@ +EXEC tSQLt.NewTestClass 'InstallAssemblyKeyTests'; +GO +DECLARE @cmd NVARCHAR(MAX); +SET @cmd = 'IF(SUSER_ID(''InstallAssemblyKeyTestsUser1'')) IS NOT NULL DROP LOGIN InstallAssemblyKeyTestsUser1;'; +EXEC master.sys.sp_executesql @cmd; +SET @cmd = 'IF(SCHEMA_ID(''InstallAssemblyKeyTestsUser1'')) IS NOT NULL DROP SCHEMA InstallAssemblyKeyTestsUser1;'; +EXEC master.sys.sp_executesql @cmd; +EXEC sys.sp_executesql @cmd; +SET @cmd = 'IF(USER_ID(''InstallAssemblyKeyTestsUser1'')) IS NOT NULL DROP USER InstallAssemblyKeyTestsUser1;'; +EXEC master.sys.sp_executesql @cmd; +EXEC sys.sp_executesql @cmd; +GO +CREATE PROCEDURE InstallAssemblyKeyTests.DropExistingItems +AS +BEGIN + IF SUSER_ID('tSQLtAssemblyKey') IS NOT NULL DROP LOGIN tSQLtAssemblyKey; + EXEC master.sys.sp_executesql N'IF ASYMKEY_ID(''tSQLtAssemblyKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtAssemblyKey;'; + EXEC master.sys.sp_executesql N'IF EXISTS(SELECT * FROM sys.assemblies WHERE name = ''tSQLtAssemblyKey'') DROP ASSEMBLY tSQLtAssemblyKey;'; + + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + IF(@ProductMajorVersion>=14) + BEGIN + DECLARE @cmd NVARCHAR(MAX); + SELECT @cmd = + ' + DECLARE @cmd NVARCHAR(MAX); + SELECT @cmd = + ( + SELECT ''EXEC sys.sp_drop_trusted_assembly @hash = '' + CONVERT(NVARCHAR(MAX),TA.[hash],1) + '';'' FROM sys.trusted_assemblies AS TA + FOR XML PATH(''''),TYPE + ).value(''.'',''NVARCHAR(MAX)''); + EXEC(@cmd); + '; + EXEC(@cmd); + END; +--SELECT * FROM sys.trusted_assemblies AS TA +END; +GO +CREATE PROCEDURE InstallAssemblyKeyTests.[test tSQLtAssemblyKey install data is signed with same key as tSQLt.clr] +AS +BEGIN + DECLARE @EAKey VARBINARY(100); + DECLARE @tSQLtKey VARBINARY(100); + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyThumbPrint = @EAKey OUT; + SELECT @tSQLtKey = I.ClrSigningKey + FROM tSQLt.Info() AS I; + EXEC tSQLt.AssertEquals @Expected = @tSQLtKey, @Actual = @EAKey; +END; +GO +CREATE PROCEDURE InstallAssemblyKeyTests.[test creates correct asymmetric key in master] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + EXEC tSQLt.InstallAssemblyKey; + + DECLARE @KeyInfoPattern NVARCHAR(MAX); + + SELECT @KeyInfoPattern = '%publickeytoken='+LOWER(CONVERT(NVARCHAR(MAX),AK.thumbprint,2))+',%' + FROM master.sys.asymmetric_keys AS AK + WHERE AK.name = 'tSQLtAssemblyKey'; + + + DECLARE @tSQLtCLRInfo VARCHAR(MAX); + SELECT @tSQLtCLRInfo = A.clr_name FROM sys.assemblies AS A WHERE name = 'tSQLtCLR'; + + EXEC tSQLt.AssertLike @ExpectedPattern = @KeyInfoPattern, @Actual = @tSQLtCLRInfo; +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test works if any assembly with the name tSQLtAssemblyKey already exists] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + EXEC tSQLt.InstallAssemblyKey; + + DECLARE @cmd NVARCHAR(MAX); + + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + IF(@ProductMajorVersion>=14) + BEGIN + -- sp_add_trusted_assembly is new (and required) in 2017 + SELECT @cmd = + 'EXEC sys.sp_add_trusted_assembly @hash = ' + CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',PGEAKB.UnsignedEmptyBytes),1) + ', @description = N''tSQLt Ephemeral'';' + FROM tSQLt_testutil.GetUnsignedEmptyBytes() AS PGEAKB; + + EXEC master.sys.sp_executesql @cmd; + END; + + SELECT @cmd = + 'CREATE ASSEMBLY tSQLtAssemblyKey AUTHORIZATION dbo FROM ' + + CONVERT(NVARCHAR(MAX),GUEB.UnsignedEmptyBytes,1) + + ' WITH PERMISSION_SET = SAFE;' + FROM tSQLt_testutil.GetUnsignedEmptyBytes() AS GUEB; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.ExpectNoException; + EXEC tSQLt.InstallAssemblyKey; + +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test works if a tSQLtAssemblyKey assembly already exists under different name] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + IF(@ProductMajorVersion>=14) + BEGIN + -- sp_add_trusted_assembly is new (and required) in 2019 + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + + SELECT @cmd = + 'EXEC sys.sp_add_trusted_assembly @hash = ' + CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1) + ', @description = N''tSQLt Ephemeral'';'; + + EXEC master.sys.sp_executesql @cmd; + END; + + SELECT @cmd = + 'CREATE ASSEMBLY [tSQLtAssemblyKey with wrong name!] AUTHORIZATION dbo FROM ' + + CONVERT(NVARCHAR(MAX),@AssemblyKeyBytes,1) + + ' WITH PERMISSION_SET = SAFE;'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.InstallAssemblyKey; + +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test drops assembly when done] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + EXEC tSQLt.InstallAssemblyKey; + + SELECT * + INTO #Actual + FROM master.sys.assemblies AS A + WHERE A.name = 'tSQLtAssemblyKey' + OR A.clr_name LIKE 'tsqltAssemblyKey, %'; + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test creates login based on the new asymmetric key] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + EXEC tSQLt.InstallAssemblyKey; + + SELECT SP.name login_name, + AK.name asymmetric_key_name, + SP.type_desc + INTO #Actual + FROM master.sys.server_principals AS SP + JOIN master.sys.asymmetric_keys AS AK + ON SP.sid = AK.sid + WHERE SP.name = 'tSQLtAssemblyKey' + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + INSERT INTO #Expected + VALUES('tSQLtAssemblyKey','tSQLtAssemblyKey','ASYMMETRIC_KEY_MAPPED_LOGIN'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test works if a tSQLtAssemblyKey asymmetric key already exists] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + + SELECT @cmd = 'CREATE ASYMMETRIC KEY tSQLtAssemblyKey WITH ALGORITHM = RSA_2048 ENCRYPTION BY PASSWORD = '''+(SELECT PW FROM tSQLt_testutil.GenerateRandomPassword(NEWID()))+''';'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.InstallAssemblyKey; + +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test works if a tSQLtAssemblyKey asymmetric key already exists under different name] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + + DECLARE @cmd NVARCHAR(MAX); + + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + IF(@ProductMajorVersion>=14) + BEGIN + -- sp_add_trusted_assembly is new (and required) in 2019 + SELECT @cmd = + 'EXEC sys.sp_add_trusted_assembly @hash = ' + CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1) + ', @description = N''tSQLt Ephemeral'';'; + + EXEC master.sys.sp_executesql @cmd; + END; + + SELECT @cmd = + 'CREATE ASSEMBLY [tSQLtAssemblyKey with wrong name!] AUTHORIZATION dbo FROM ' + + CONVERT(NVARCHAR(MAX),@AssemblyKeyBytes,1) + + ' WITH PERMISSION_SET = SAFE;'; + EXEC master.sys.sp_executesql @cmd; + + SET @cmd = 'CREATE ASYMMETRIC KEY [tSQLtAssemblyKey->asymmetric key with wrong name!] FROM ASSEMBLY [tSQLtAssemblyKey with wrong name!];'; + EXEC master.sys.sp_executesql @cmd; + + SET @cmd = 'DROP ASSEMBLY [tSQLtAssemblyKey with wrong name!];'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.InstallAssemblyKey; +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test works if a tSQLtAssemblyKey asymmetric key/login pair already exists under different name] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + + DECLARE @cmd NVARCHAR(MAX); + + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + IF(@ProductMajorVersion>=14) + BEGIN + -- sp_add_trusted_assembly is new (and required) in 2019 + SELECT @cmd = + 'EXEC sys.sp_add_trusted_assembly @hash = ' + CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1) + ', @description = N''tSQLt Ephemeral'';'; + + EXEC master.sys.sp_executesql @cmd; + END; + + SELECT @cmd = + 'CREATE ASSEMBLY [tSQLtAssemblyKey with wrong name!] AUTHORIZATION dbo FROM ' + + CONVERT(NVARCHAR(MAX),@AssemblyKeyBytes,1) + + ' WITH PERMISSION_SET = SAFE;'; + EXEC master.sys.sp_executesql @cmd; + + SET @cmd = 'CREATE ASYMMETRIC KEY [tSQLtAssemblyKey->asymmetric key with wrong name!] FROM ASSEMBLY [tSQLtAssemblyKey with wrong name!];'; + EXEC master.sys.sp_executesql @cmd; + + SET @cmd = 'CREATE LOGIN [tSQLtAssemblyKey->server principal with wrong name!] FROM ASYMMETRIC KEY [tSQLtAssemblyKey->asymmetric key with wrong name!];'; + EXEC master.sys.sp_executesql @cmd; + + SET @cmd = 'DROP ASSEMBLY [tSQLtAssemblyKey with wrong name!];'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.InstallAssemblyKey; + +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test works if a tSQLtAssemblyKey login already exists] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + + SELECT @cmd = 'CREATE LOGIN tSQLtAssemblyKey WITH PASSWORD = '''+(SELECT PW FROM tSQLt_testutil.GenerateRandomPassword(NEWID()))+''',CHECK_EXPIRATION = OFF, CHECK_POLICY = OFF;'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.InstallAssemblyKey; + +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test non-sysadmin cannot execute procedure] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + + SET @cmd = 'CREATE LOGIN InstallAssemblyKeyTestsUser1 WITH PASSWORD='''+(SELECT PW FROM tSQLt_testutil.GenerateRandomPassword(NEWID()))+''';'; + EXEC master.sys.sp_executesql @cmd; + + SET @cmd = 'CREATE USER InstallAssemblyKeyTestsUser1;ALTER ROLE db_owner ADD MEMBER InstallAssemblyKeyTestsUser1;'; + IF((SELECT I.SqlVersion FROM tSQLt.Info() AS I) < 11) + BEGIN + SET @cmd = 'CREATE USER InstallAssemblyKeyTestsUser1;EXEC sys.sp_addrolemember @rolename = ''db_owner'', @membername = ''InstallAssemblyKeyTestsUser1'';'; + END + EXEC master.sys.sp_executesql @cmd; + EXEC sys.sp_executesql @cmd; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Only principals with CONTROL SERVER permission can execute this procedure.'; + + EXECUTE AS LOGIN = 'InstallAssemblyKeyTestsUser1'; + EXEC tSQLt.InstallAssemblyKey; + REVERT; + +END; +GO + +CREATE PROCEDURE InstallAssemblyKeyTests.[test sysadmin can execute procedure] +AS +BEGIN + + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + + SET @cmd = 'CREATE LOGIN InstallAssemblyKeyTestsUser1 WITH PASSWORD='''+(SELECT PW FROM tSQLt_testutil.GenerateRandomPassword(NEWID()))+''';'; + EXEC master.sys.sp_executesql @cmd; + + SET @cmd = 'GRANT CONTROL SERVER TO InstallAssemblyKeyTestsUser1;'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.ExpectNoException; + + EXECUTE AS LOGIN = 'InstallAssemblyKeyTestsUser1'; + EXEC tSQLt.InstallAssemblyKey; + REVERT; + +END; +GO +--[@tSQLt:RunOnlyOnHostPlatform]('Windows') +CREATE PROCEDURE InstallAssemblyKeyTests.[test tSQLt can be set to EXTERNAL ACCESS after InstallAssemblyKey executed] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + EXEC tSQLt.InstallAssemblyKey; + + EXEC tSQLt.ExpectNoException; + + ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = EXTERNAL_ACCESS; + +END; +GO + +------------------------------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------------------------------------------ +---- run following tests with clr strict security = 1 ------------------------------------------------------------ +------------------------------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------------------------------------------ +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE InstallAssemblyKeyTests.[test can install key even if clr strict security is set to 1] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + EXEC tSQLt.InstallAssemblyKey; + + SELECT SP.name login_name, + AK.name asymmetric_key_name, + SP.type_desc + INTO #Actual + FROM master.sys.server_principals AS SP + JOIN master.sys.asymmetric_keys AS AK + ON SP.sid = AK.sid + WHERE SP.name = 'tSQLtAssemblyKey' + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + INSERT INTO #Expected + VALUES('tSQLtAssemblyKey','tSQLtAssemblyKey','ASYMMETRIC_KEY_MAPPED_LOGIN'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE InstallAssemblyKeyTests.[test works if trusted assembly entry exists already] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + + DECLARE @cmd NVARCHAR(MAX); + SELECT @cmd = + 'EXEC sys.sp_add_trusted_assembly @hash = ' + CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1) + ', @description = N''tSQLt Ephemeral'';'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.ExpectNoException; + EXEC tSQLt.InstallAssemblyKey; + +END; +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE InstallAssemblyKeyTests.[test removes trusted assembly record when done] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + EXEC tSQLt.InstallAssemblyKey; + + SELECT * INTO #Actual FROM sys.trusted_assemblies AS TA WHERE TA.description = 'tSQLt Ephemeral'; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; + +END; +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE InstallAssemblyKeyTests.[test does not remove trusted assembly record if it already exists (based on hash)] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + + DECLARE @Hash NVARCHAR(MAX) = CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1); + + DECLARE @cmd NVARCHAR(MAX); + SELECT @cmd = 'EXEC sys.sp_add_trusted_assembly @hash = ' + @Hash + ', @description = N''some random description'';' + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.InstallAssemblyKey; + + SELECT TA.hash, TA.description INTO #Actual FROM sys.trusted_assemblies AS TA; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(CONVERT(VARBINARY(64),@Hash,1), 'some random description'); --failing this on purpose. Fix me. + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE InstallAssemblyKeyTests.[test removes trusted assembly record if it already exists (based on hash) and has the tSQLt Ephemeral description] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + + DECLARE @Hash NVARCHAR(MAX) = CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1); + + DECLARE @cmd NVARCHAR(MAX); + SELECT @cmd = 'EXEC sys.sp_add_trusted_assembly @hash = ' + @Hash + ', @description = N''tSQLt Ephemeral'';' + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.InstallAssemblyKey; + + SELECT * INTO #Actual FROM sys.trusted_assemblies AS TA WHERE TA.description = 'tSQLt Ephemeral'; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](13) +CREATE PROCEDURE InstallAssemblyKeyTests.[test grants EXTERNAL ACCESS ASSEMBLY permission to new login pre SQL 2017] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + EXEC tSQLt.InstallAssemblyKey; + + SELECT SP.class_desc, + SP.permission_name, + SP.state_desc + INTO #Actual + FROM sys.server_permissions AS SP + WHERE SP.grantee_principal_id = SUSER_ID('tSQLtAssemblyKey') + AND SP.permission_name LIKE '%ASSEMBLY' + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + VALUES('SERVER','EXTERNAL ACCESS ASSEMBLY','GRANT'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE InstallAssemblyKeyTests.[test grants UNSAFE ASSEMBLY permission to new login in SQL 2017 and later] +AS +BEGIN + EXEC InstallAssemblyKeyTests.DropExistingItems; + + EXEC tSQLt.InstallAssemblyKey; + + SELECT SP.class_desc, + SP.permission_name, + SP.state_desc + INTO #Actual + FROM sys.server_permissions AS SP + WHERE SP.grantee_principal_id = SUSER_ID('tSQLtAssemblyKey') + AND SP.permission_name LIKE '%ASSEMBLY' + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + VALUES('SERVER','UNSAFE ASSEMBLY','GRANT'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO diff --git a/Tests.SA/InstallExternalAccessKeyTests.class.sql b/Tests.SA/InstallExternalAccessKeyTests.class.sql index d34620990..16ce6ceb7 100644 --- a/Tests.SA/InstallExternalAccessKeyTests.class.sql +++ b/Tests.SA/InstallExternalAccessKeyTests.class.sql @@ -1,310 +1,37 @@ EXEC tSQLt.NewTestClass 'InstallExternalAccessKeyTests'; GO -DECLARE @cmd NVARCHAR(MAX); -SET @cmd = 'IF(SUSER_ID(''InstallExternalAccessKeyTestsUser1'')) IS NOT NULL DROP LOGIN InstallExternalAccessKeyTestsUser1;'; -EXEC master.sys.sp_executesql @cmd; -SET @cmd = 'IF(SCHEMA_ID(''InstallExternalAccessKeyTestsUser1'')) IS NOT NULL DROP SCHEMA InstallExternalAccessKeyTestsUser1;'; -EXEC master.sys.sp_executesql @cmd; -EXEC sys.sp_executesql @cmd; -SET @cmd = 'IF(USER_ID(''InstallExternalAccessKeyTestsUser1'')) IS NOT NULL DROP USER InstallExternalAccessKeyTestsUser1;'; -EXEC master.sys.sp_executesql @cmd; -EXEC sys.sp_executesql @cmd; -GO -CREATE PROCEDURE InstallExternalAccessKeyTests.DropExistingItems -AS -BEGIN - IF SUSER_ID('tSQLtExternalAccessKey') IS NOT NULL DROP LOGIN tSQLtExternalAccessKey; - EXEC master.sys.sp_executesql N'IF ASYMKEY_ID(''tSQLtExternalAccessKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtExternalAccessKey;'; - EXEC master.sys.sp_executesql N'IF EXISTS(SELECT * FROM sys.assemblies WHERE name = ''tSQLtExternalAccessKey'') DROP ASSEMBLY tSQLtExternalAccessKey;'; -END; -GO -CREATE PROCEDURE InstallExternalAccessKeyTests.[test tSQLtExternalAccessKey install data is signed with same key as tSQLt.clr] -AS -BEGIN - DECLARE @EAKey VARBINARY(100); - DECLARE @tSQLtKey VARBINARY(100); - SELECT @EAKey = PGEAKB.ExternalAccessKeyThumbPrint - FROM tSQLt.Private_GetExternalAccessKeyBytes() AS PGEAKB; - SELECT @tSQLtKey = I.ClrSigningKey - FROM tSQLt.Info() AS I; - EXEC tSQLt.AssertEquals @Expected = @tSQLtKey, @Actual = @EAKey; -END; -GO -CREATE PROCEDURE InstallExternalAccessKeyTests.[test creates correct certificate in master] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - EXEC tSQLt.InstallExternalAccessKey; - - DECLARE @KeyInfoPattern NVARCHAR(MAX); - - SELECT @KeyInfoPattern = '%publickeytoken='+BH.bare+',%' - FROM master.sys.asymmetric_keys AS AK - CROSS APPLY tSQLt.Private_Bin2Hex(AK.thumbprint) AS BH - WHERE AK.name = 'tSQLtExternalAccessKey'; - - - DECLARE @tSQLtCLRInfo VARCHAR(MAX); - SELECT @tSQLtCLRInfo = A.clr_name FROM sys.assemblies AS A WHERE name = 'tSQLtCLR'; - - EXEC tSQLt.AssertLike @ExpectedPattern = @KeyInfoPattern, @Actual = @tSQLtCLRInfo; -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test works if a tSQLtExternalAccessKey assembly already exists] +CREATE PROCEDURE InstallExternalAccessKeyTests.[test calls tSQLt.InstallAssemblyKey] AS BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.InstallAssemblyKey'; - SELECT @cmd = - 'CREATE ASSEMBLY tSQLtExternalAccessKey AUTHORIZATION dbo FROM ' + - BH.prefix + - ' WITH PERMISSION_SET = SAFE;' - FROM tSQLt_testutil.GetUnsignedEmptyBytes() AS GUEB - CROSS APPLY tSQLt.Private_Bin2Hex(GUEB.UnsignedEmptyBytes) AS BH; - EXEC master.sys.sp_executesql @cmd; - EXEC tSQLt.InstallExternalAccessKey; -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test works if a tSQLtExternalAccessKey assembly already exists under different name] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); + SELECT _id_ INTO #Actual FROM tSQLt.InstallAssemblyKey_SpyProcedureLog; - SELECT @cmd = - 'CREATE ASSEMBLY [tSQLtExternalAccessKey with wrong name!] AUTHORIZATION dbo FROM ' + - BH.prefix + - ' WITH PERMISSION_SET = SAFE;' - FROM tSQLt.Private_GetExternalAccessKeyBytes() AS PGEAKB - CROSS APPLY tSQLt.Private_Bin2Hex(PGEAKB.ExternalAccessKeyBytes) AS BH; - EXEC master.sys.sp_executesql @cmd; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - EXEC tSQLt.InstallExternalAccessKey; - -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test drops assembly when done] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - EXEC tSQLt.InstallExternalAccessKey; - - SELECT * - INTO #Actual - FROM master.sys.assemblies AS A - WHERE A.name = 'tSQLtExternalAccessKey' - OR A.clr_name LIKE 'tsqltexternalaccesskey, %'; - EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test creates login based on the new certificate] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - EXEC tSQLt.InstallExternalAccessKey; - - SELECT SP.name login_name, - AK.name certificate_name, - SP.type_desc - INTO #Actual - FROM master.sys.server_principals AS SP - JOIN master.sys.asymmetric_keys AS AK - ON SP.sid = AK.sid - WHERE SP.name = 'tSQLtExternalAccessKey' - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - INSERT INTO #Expected - VALUES('tSQLtExternalAccessKey','tSQLtExternalAccessKey','ASYMMETRIC_KEY_MAPPED_LOGIN'); + INSERT INTO #Expected VALUES (1); EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test works if a tSQLtExternalAccessKey certificate already exists] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); - - SELECT @cmd = 'CREATE ASYMMETRIC KEY tSQLtExternalAccessKey WITH ALGORITHM = RSA_2048 ENCRYPTION BY PASSWORD = '''+CAST(NEWID() AS NVARCHAR(MAX))+''';'; - EXEC master.sys.sp_executesql @cmd; - EXEC tSQLt.InstallExternalAccessKey; - END; GO -CREATE PROCEDURE InstallExternalAccessKeyTests.[test works if a tSQLtExternalAccessKey certificate already exists under different name] +CREATE PROCEDURE InstallExternalAccessKeyTests.[test prints sensible deprecation warning] AS BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); - - SELECT @cmd = - 'CREATE ASSEMBLY [tSQLtExternalAccessKey with wrong name!] AUTHORIZATION dbo FROM ' + - BH.prefix + - ' WITH PERMISSION_SET = SAFE;' - FROM tSQLt.Private_GetExternalAccessKeyBytes() AS PGEAKB - CROSS APPLY tSQLt.Private_Bin2Hex(PGEAKB.ExternalAccessKeyBytes) AS BH; - EXEC master.sys.sp_executesql @cmd; - - SET @cmd = 'CREATE ASYMMETRIC KEY [tSQLtExternalAccessKey->asymmetric key with wrong name!] FROM ASSEMBLY [tSQLtExternalAccessKey with wrong name!];'; - EXEC master.sys.sp_executesql @cmd; - - SET @cmd = 'DROP ASSEMBLY [tSQLtExternalAccessKey with wrong name!];'; - EXEC master.sys.sp_executesql @cmd; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.InstallAssemblyKey'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; EXEC tSQLt.InstallExternalAccessKey; -END; -GO + SELECT Message INTO #Actual FROM tSQLt.Private_Print_SpyProcedureLog ; -CREATE PROCEDURE InstallExternalAccessKeyTests.[test works if a tSQLtExternalAccessKey certificate/login pair already exists under different name] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); - - SELECT @cmd = - 'CREATE ASSEMBLY [tSQLtExternalAccessKey with wrong name!] AUTHORIZATION dbo FROM ' + - BH.prefix + - ' WITH PERMISSION_SET = SAFE;' - FROM tSQLt.Private_GetExternalAccessKeyBytes() AS PGEAKB - CROSS APPLY tSQLt.Private_Bin2Hex(PGEAKB.ExternalAccessKeyBytes) AS BH; - EXEC master.sys.sp_executesql @cmd; - - SET @cmd = 'CREATE ASYMMETRIC KEY [tSQLtExternalAccessKey->asymmetric key with wrong name!] FROM ASSEMBLY [tSQLtExternalAccessKey with wrong name!];'; - EXEC master.sys.sp_executesql @cmd; - - SET @cmd = 'CREATE LOGIN [tSQLtExternalAccessKey->server principal with wrong name!] FROM ASYMMETRIC KEY [tSQLtExternalAccessKey->asymmetric key with wrong name!];'; - EXEC master.sys.sp_executesql @cmd; - - SET @cmd = 'DROP ASSEMBLY [tSQLtExternalAccessKey with wrong name!];'; - EXEC master.sys.sp_executesql @cmd; - - EXEC tSQLt.InstallExternalAccessKey; - -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test works if a tSQLtExternalAccessKey login already exists] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); - - SELECT @cmd = 'CREATE LOGIN tSQLtExternalAccessKey WITH PASSWORD = '''+CAST(NEWID() AS NVARCHAR(MAX))+''';'; - EXEC master.sys.sp_executesql @cmd; - - EXEC tSQLt.InstallExternalAccessKey; - -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test grants EXTERNAL ACCESS ASSEMBLY permission to new login] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - EXEC tSQLt.InstallExternalAccessKey; - - SELECT SP.class_desc, - SP.permission_name, - SP.state_desc - INTO #Actual - FROM sys.server_permissions AS SP - WHERE SP.grantee_principal_id = SUSER_ID('tSQLtExternalAccessKey') - AND SP.permission_name = 'EXTERNAL ACCESS ASSEMBLY' - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - INSERT INTO #Expected - VALUES('SERVER','EXTERNAL ACCESS ASSEMBLY','GRANT'); - - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test non-sysadmin cannot execute procedure] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); - - SET @cmd = 'CREATE LOGIN InstallExternalAccessKeyTestsUser1 WITH PASSWORD=''(*&^#@($&^%(&@#^$%(!&@'';'; - EXEC master.sys.sp_executesql @cmd; - - SET @cmd = 'CREATE USER InstallExternalAccessKeyTestsUser1;ALTER ROLE db_owner ADD MEMBER InstallExternalAccessKeyTestsUser1;'; - IF((SELECT I.SqlVersion FROM tSQLt.Info() AS I) < 11) - BEGIN - SET @cmd = 'CREATE USER InstallExternalAccessKeyTestsUser1;EXEC sys.sp_addrolemember @rolename = ''db_owner'', @membername = ''InstallExternalAccessKeyTestsUser1'';'; - END - EXEC master.sys.sp_executesql @cmd; - EXEC sys.sp_executesql @cmd; - - EXEC tSQLt.ExpectException @ExpectedMessage = 'Only principals with CONTROL SERVER permission can execute this procedure.'; - - EXECUTE AS LOGIN = 'InstallExternalAccessKeyTestsUser1'; - EXEC tSQLt.InstallExternalAccessKey; - REVERT; - -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test sysadmin can execute procedure] -AS -BEGIN + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('tSQLt.InstallExternalAccessKey is deprecated. Please use tSQLt.InstallAssemblyKey instead.'); - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); - - SET @cmd = 'CREATE LOGIN InstallExternalAccessKeyTestsUser1 WITH PASSWORD=''(*&^#@($&^%(&@#^$%(!&@'';'; - EXEC master.sys.sp_executesql @cmd; - - SET @cmd = 'GRANT CONTROL SERVER TO InstallExternalAccessKeyTestsUser1;'; - EXEC master.sys.sp_executesql @cmd; - - EXEC tSQLt.ExpectNoException; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; - EXECUTE AS LOGIN = 'InstallExternalAccessKeyTestsUser1'; - EXEC tSQLt.InstallExternalAccessKey; - REVERT; - -END; -GO - -CREATE PROCEDURE InstallExternalAccessKeyTests.[test tSQLt can be set to EXTERNAL ACCESS after InstallExternalAccessKey executed] -AS -BEGIN - EXEC InstallExternalAccessKeyTests.DropExistingItems; - - EXEC tSQLt.InstallExternalAccessKey; - - EXEC tSQLt.ExpectNoException; - - ALTER ASSEMBLY tSQLtCLR WITH PERMISSION_SET = EXTERNAL_ACCESS; - END; GO diff --git a/Tests.SA/PrepareServerTests.class.sql b/Tests.SA/PrepareServerTests.class.sql new file mode 100644 index 000000000..2e482c2c5 --- /dev/null +++ b/Tests.SA/PrepareServerTests.class.sql @@ -0,0 +1,55 @@ +EXEC tSQLt.NewTestClass 'PrepareServerTests'; +GO +CREATE PROCEDURE PrepareServerTests.[test enables CLR] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_EnableCLR'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.InstallAssemblyKey'; + + + EXEC tSQLt.PrepareServer; + + SELECT COUNT(1) NumberOfCalls + INTO #Actual + FROM tSQLt.Private_EnableCLR_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE PrepareServerTests.[test calls tSQLt.InstallAssemblyKey] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_EnableCLR'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.InstallAssemblyKey'; + + EXEC tSQLt.PrepareServer; + + SELECT COUNT(1) NumberOfCalls + INTO #Actual + FROM tSQLt.InstallAssemblyKey_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE PrepareServerTests.[test calls tSQLt.InstallAssemblyKey after tSQLt.Private_EnableCLR] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_EnableCLR', @CommandToExecute='INSERT INTO #Actual VALUES (''tSQLt.Private_EnableCLR'')'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.InstallAssemblyKey', @CommandToExecute='INSERT INTO #Actual VALUES (''tSQLt.InstallAssemblyKey'')'; + + CREATE TABLE #Actual ( OrderNo INT IDENTITY (1,1), ProcedureName NVARCHAR(Max)); + + EXEC tSQLt.PrepareServer; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'tSQLt.Private_EnableCLR'),(2,'tSQLt.InstallAssemblyKey'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO \ No newline at end of file diff --git a/Tests.SA/Private_EnableCLRTests.class.sql b/Tests.SA/Private_EnableCLRTests.class.sql new file mode 100644 index 000000000..bc0c3c0da --- /dev/null +++ b/Tests.SA/Private_EnableCLRTests.class.sql @@ -0,0 +1,58 @@ +EXEC tSQLt.NewTestClass 'Private_EnableCLRTests'; +GO +CREATE PROCEDURE Private_EnableCLRTests.[test alters value for 'clr enabled' in serverconfigurations] +AS +BEGIN + EXEC tSQLt.CaptureOutput 'EXEC sys.sp_configure @configname=''clr enabled'', @configvalue=0;'; + + BEGIN TRY + EXEC tSQLt.Private_EnableCLR; + END TRY + BEGIN CATCH + PRINT ERROR_MESSAGE(); + END CATCH + + SELECT name,value + INTO #Actual + FROM sys.configurations AS C + WHERE C.name = 'clr enabled'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('clr enabled',1); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE Private_EnableCLRTests.[test does not alter other values in serverconfigurations] +AS +BEGIN + SELECT C.name, + C.value + INTO #Expected + FROM master.sys.configurations AS C + WHERE name <> 'clr enabled'; + + BEGIN TRY + EXEC tSQLt.Private_EnableCLR; + END TRY + BEGIN CATCH + PRINT ERROR_MESSAGE(); + END CATCH + + SELECT name,value + INTO #Actual + FROM sys.configurations AS C + WHERE C.name <> 'clr enabled'; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE Private_EnableCLRTests.[test calls RECONFIGURE] +AS +BEGIN + EXEC tSQLt.ExpectException @ExpectedMessage = 'CONFIG statement cannot be used inside a user transaction.'; + + EXEC tSQLt.Private_EnableCLR; +END; +GO diff --git a/Tests.SA/RemoveAssemblyKeyTests.class.sql b/Tests.SA/RemoveAssemblyKeyTests.class.sql new file mode 100644 index 000000000..6e52efdbf --- /dev/null +++ b/Tests.SA/RemoveAssemblyKeyTests.class.sql @@ -0,0 +1,256 @@ +EXEC tSQLt.NewTestClass 'RemoveAssemblyKeyTests'; +GO +CREATE PROCEDURE RemoveAssemblyKeyTests.RemoveKeyTestUser +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + SET @cmd = 'IF(SUSER_ID(''RemoveAssemblyKeyTestsUser1'')) IS NOT NULL DROP LOGIN RemoveAssemblyKeyTestsUser1;'; + EXEC master.sys.sp_executesql @cmd; + SET @cmd = 'IF(SCHEMA_ID(''RemoveAssemblyKeyTestsUser1'')) IS NOT NULL DROP SCHEMA RemoveAssemblyKeyTestsUser1;'; + EXEC master.sys.sp_executesql @cmd; + EXEC sys.sp_executesql @cmd; + SET @cmd = 'IF(USER_ID(''RemoveAssemblyKeyTestsUser1'')) IS NOT NULL DROP USER RemoveAssemblyKeyTestsUser1;'; + EXEC master.sys.sp_executesql @cmd; + EXEC sys.sp_executesql @cmd; +END; +GO +CREATE PROCEDURE RemoveAssemblyKeyTests.DropExistingItems +AS +BEGIN + IF SUSER_ID('tSQLtAssemblyKey') IS NOT NULL DROP LOGIN tSQLtAssemblyKey; + EXEC master.sys.sp_executesql N'IF ASYMKEY_ID(''tSQLtAssemblyKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtAssemblyKey;'; + EXEC master.sys.sp_executesql N'IF EXISTS(SELECT * FROM sys.assemblies WHERE name = ''tSQLtAssemblyKey'') DROP ASSEMBLY tSQLtAssemblyKey;'; + DECLARE @cmd NVARCHAR(MAX); + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + IF(@ProductMajorVersion>=14) + BEGIN + -- sp_drop_trusted_assembly is new (and required) in 2017 + DECLARE @TrustedHash NVARCHAR(MAX); + SELECT @TrustedHash = CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',PGEAKB.UnsignedEmptyBytes),1) + FROM tSQLt_testutil.GetUnsignedEmptyBytes() AS PGEAKB + + SELECT @cmd = + 'IF EXISTS(SELECT 1 FROM sys.trusted_assemblies WHERE hash = ' + @TrustedHash +')'+ + 'EXEC sys.sp_drop_trusted_assembly @hash = ' + @TrustedHash + ';'; + EXEC master.sys.sp_executesql @cmd; + + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + + SELECT @TrustedHash = CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1); + + SELECT @cmd = + 'IF EXISTS(SELECT 1 FROM sys.trusted_assemblies WHERE hash = ' + @TrustedHash +')'+ + 'EXEC sys.sp_drop_trusted_assembly @hash = ' + @TrustedHash + ';'; + EXEC master.sys.sp_executesql @cmd; + END; +END; +GO +GO +CREATE PROCEDURE RemoveAssemblyKeyTests.[test removes tSQLtAssemblyKey assembly] +AS +BEGIN + EXEC RemoveAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + IF(@ProductMajorVersion>=14) + BEGIN + -- sp_add_trusted_assembly is new (and required) in 2017 + SELECT @cmd = + 'EXEC sys.sp_add_trusted_assembly @hash = ' + CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',PGEAKB.UnsignedEmptyBytes),1) + ', @description = N''tSQLt Ephemeral'';' + FROM tSQLt_testutil.GetUnsignedEmptyBytes() AS PGEAKB + WHERE NOT EXISTS(SELECT 1 FROM sys.trusted_assemblies AS TA WHERE TA.hash = HASHBYTES('SHA2_512',PGEAKB.UnsignedEmptyBytes)); + + EXEC master.sys.sp_executesql @cmd; + END; + + SELECT @cmd = + 'CREATE ASSEMBLY tSQLtAssemblyKey AUTHORIZATION dbo FROM ' + + CONVERT(NVARCHAR(MAX),GUEB.UnsignedEmptyBytes,1) + + ' WITH PERMISSION_SET = SAFE;' + FROM tSQLt_testutil.GetUnsignedEmptyBytes() AS GUEB; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.RemoveAssemblyKey; + + IF(EXISTS(SELECT 1 FROM master.sys.assemblies WHERE name = 'tSQLtAssemblyKey')) + BEGIN + EXEC tSQLt.Fail 'Assembly tSQLtAssemblyKey not removed.'; + END; +END; +GO + +CREATE PROCEDURE RemoveAssemblyKeyTests.[test removes tSQLtAssemblyKey asymmetric key] +AS +BEGIN + EXEC RemoveAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + + SELECT @cmd = 'CREATE ASYMMETRIC KEY tSQLtAssemblyKey WITH ALGORITHM = RSA_2048 ENCRYPTION BY PASSWORD = '''+(SELECT PW FROM tSQLt_testutil.GenerateRandomPassword(NEWID()))+''';'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.RemoveAssemblyKey; + + SELECT * + INTO #Actual + FROM master.sys.asymmetric_keys AS AK + WHERE AK.name = 'tSQLtAssemblyKey' + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; --<-- this might blow up if the table isn't empty. +END; +GO + +CREATE PROCEDURE RemoveAssemblyKeyTests.[test removes tSQLtAssemblyKey login] +AS +BEGIN + EXEC RemoveAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + + SELECT @cmd = 'CREATE LOGIN tSQLtAssemblyKey WITH PASSWORD = '''+(SELECT PW FROM tSQLt_testutil.GenerateRandomPassword(NEWID()))+''',CHECK_EXPIRATION = OFF, CHECK_POLICY = OFF;'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.RemoveAssemblyKey; + + SELECT * + INTO #Actual + FROM master.sys.server_principals AS SP + WHERE SP.name = 'tSQLtAssemblyKey' + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; --<-- this might blow up if the table isn't empty. +END; +GO + +CREATE PROCEDURE RemoveAssemblyKeyTests.[test removes tSQLtAssemblyKey asymmetric key and login in correct order] +AS +BEGIN + EXEC RemoveAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + + SELECT @cmd = 'CREATE ASYMMETRIC KEY tSQLtAssemblyKey WITH ALGORITHM = RSA_2048 ENCRYPTION BY PASSWORD = '''+(SELECT PW FROM tSQLt_testutil.GenerateRandomPassword(NEWID()))+''';'; + EXEC master.sys.sp_executesql @cmd; + SELECT @cmd = 'CREATE LOGIN tSQLtAssemblyKey FROM ASYMMETRIC KEY tSQLtAssemblyKey;'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.RemoveAssemblyKey; + + SELECT * + INTO #Actual + FROM + ( + SELECT AK.name + FROM master.sys.asymmetric_keys AS AK + WHERE AK.name = 'tSQLtAssemblyKey' + UNION ALL + SELECT SP.name + FROM master.sys.server_principals AS SP + WHERE SP.name = 'tSQLtAssemblyKey' + )A + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; --<-- this might blow up if the table isn't empty. +END; +GO + +CREATE PROCEDURE RemoveAssemblyKeyTests.[test non-sysadmin cannot execute procedure] +AS +BEGIN + EXEC RemoveAssemblyKeyTests.RemoveKeyTestUser; + + DECLARE @cmd NVARCHAR(MAX); + + SET @cmd = 'CREATE LOGIN RemoveAssemblyKeyTestsUser1 WITH PASSWORD='''+(SELECT PW FROM tSQLt_testutil.GenerateRandomPassword(NEWID()))+''';'; + EXEC master.sys.sp_executesql @cmd; + + SET @cmd = 'CREATE USER RemoveAssemblyKeyTestsUser1;ALTER ROLE db_owner ADD MEMBER RemoveAssemblyKeyTestsUser1;'; + IF((SELECT I.SqlVersion FROM tSQLt.Info() AS I) < 11) + BEGIN + SET @cmd = 'CREATE USER RemoveAssemblyKeyTestsUser1;EXEC sys.sp_addrolemember @rolename = ''db_owner'', @membername = ''RemoveAssemblyKeyTestsUser1'';'; + END + EXEC master.sys.sp_executesql @cmd; + EXEC sys.sp_executesql @cmd; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Only principals with CONTROL SERVER permission can execute this procedure.'; + + EXECUTE AS LOGIN = 'RemoveAssemblyKeyTestsUser1'; + EXEC tSQLt.RemoveAssemblyKey; + REVERT;--<--won't actually get executed... + +END; +GO + +CREATE PROCEDURE RemoveAssemblyKeyTests.[test login with control server permission can execute procedure] +AS +BEGIN + EXEC RemoveAssemblyKeyTests.DropExistingItems; + + EXEC RemoveAssemblyKeyTests.RemoveKeyTestUser; + + DECLARE @cmd NVARCHAR(MAX); + + SET @cmd = 'CREATE LOGIN RemoveAssemblyKeyTestsUser1 WITH PASSWORD='''+(SELECT PW FROM tSQLt_testutil.GenerateRandomPassword(NEWID()))+''';'; + EXEC master.sys.sp_executesql @cmd; + + SET @cmd = 'GRANT CONTROL SERVER TO RemoveAssemblyKeyTestsUser1;'; + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.ExpectNoException; + + EXECUTE AS LOGIN = 'RemoveAssemblyKeyTestsUser1'; + EXEC tSQLt.RemoveAssemblyKey; + REVERT; +END; +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE RemoveAssemblyKeyTests.[test removes trusted assembly record if it exists] +AS +BEGIN + EXEC RemoveAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + DECLARE @TrustedHash NVARCHAR(MAX); + + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + SELECT @TrustedHash = CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1); + + SELECT @cmd = 'EXEC sys.sp_add_trusted_assembly @hash = ' + @TrustedHash +',@description = N''tSQLt Ephemeral'';' + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.RemoveAssemblyKey; + + IF(EXISTS(SELECT 1 FROM master.sys.trusted_assemblies WHERE hash = CONVERT(VARBINARY(MAX),@TrustedHash,1))) + BEGIN + EXEC tSQLt.Fail 'Trusted Assembly record for tSQLtAssemblyKey not removed.'; + END; +END; +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE RemoveAssemblyKeyTests.[test doesn't remove trusted assembly record if it is not 'tSQLt Ephemeral'] +AS +BEGIN + EXEC RemoveAssemblyKeyTests.DropExistingItems; + + DECLARE @cmd NVARCHAR(MAX); + DECLARE @AssemblyKeyBytes VARBINARY(MAX); + DECLARE @TrustedHash NVARCHAR(MAX); + + EXEC tSQLt.Private_GetAssemblyKeyBytes @AssemblyKeyBytes = @AssemblyKeyBytes OUT; + SELECT @TrustedHash = CONVERT(NVARCHAR(MAX),HASHBYTES('SHA2_512',@AssemblyKeyBytes),1); + + SELECT @cmd = 'EXEC sys.sp_add_trusted_assembly @hash = ' + @TrustedHash +',@description = N''something else'';' + EXEC master.sys.sp_executesql @cmd; + + EXEC tSQLt.RemoveAssemblyKey; + + IF(NOT EXISTS(SELECT 1 FROM master.sys.trusted_assemblies WHERE hash = CONVERT(VARBINARY(MAX),@TrustedHash,1))) + BEGIN + EXEC tSQLt.Fail 'Trusted Assembly record for tSQLtAssemblyKey removed despite description <> ''tSQLt Ephemeral''.'; + END; +END; +GO diff --git a/Tests.SA/RemoveExternalAccessKeyTests.class.sql b/Tests.SA/RemoveExternalAccessKeyTests.class.sql index 034f47511..0e9b9c5b6 100644 --- a/Tests.SA/RemoveExternalAccessKeyTests.class.sql +++ b/Tests.SA/RemoveExternalAccessKeyTests.class.sql @@ -1,167 +1,37 @@ EXEC tSQLt.NewTestClass 'RemoveExternalAccessKeyTests'; GO -DECLARE @cmd NVARCHAR(MAX); -SET @cmd = 'IF(SUSER_ID(''RemoveExternalAccessKeyTestsUser1'')) IS NOT NULL DROP LOGIN RemoveExternalAccessKeyTestsUser1;'; -EXEC master.sys.sp_executesql @cmd; -SET @cmd = 'IF(SCHEMA_ID(''RemoveExternalAccessKeyTestsUser1'')) IS NOT NULL DROP SCHEMA RemoveExternalAccessKeyTestsUser1;'; -EXEC master.sys.sp_executesql @cmd; -EXEC sys.sp_executesql @cmd; -SET @cmd = 'IF(USER_ID(''RemoveExternalAccessKeyTestsUser1'')) IS NOT NULL DROP USER RemoveExternalAccessKeyTestsUser1;'; -EXEC master.sys.sp_executesql @cmd; -EXEC sys.sp_executesql @cmd; -GO -CREATE PROCEDURE RemoveExternalAccessKeyTests.DropExistingItems -AS -BEGIN - IF SUSER_ID('tSQLtExternalAccessKey') IS NOT NULL DROP LOGIN tSQLtExternalAccessKey; - EXEC master.sys.sp_executesql N'IF ASYMKEY_ID(''tSQLtExternalAccessKey'') IS NOT NULL DROP ASYMMETRIC KEY tSQLtExternalAccessKey;'; - EXEC master.sys.sp_executesql N'IF EXISTS(SELECT * FROM sys.assemblies WHERE name = ''tSQLtExternalAccessKey'') DROP ASSEMBLY tSQLtExternalAccessKey;'; -END; -GO -GO -CREATE PROCEDURE RemoveExternalAccessKeyTests.[test removes tSQLtExternalAccessKey assembly] -AS -BEGIN - EXEC RemoveExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); - - SELECT @cmd = - 'CREATE ASSEMBLY tSQLtExternalAccessKey AUTHORIZATION dbo FROM ' + - BH.prefix + - ' WITH PERMISSION_SET = SAFE;' - FROM tSQLt_testutil.GetUnsignedEmptyBytes() AS GUEB - CROSS APPLY tSQLt.Private_Bin2Hex(GUEB.UnsignedEmptyBytes) AS BH; - EXEC master.sys.sp_executesql @cmd; - - EXEC tSQLt.RemoveExternalAccessKey; - - SELECT * - INTO #Actual - FROM master.sys.assemblies AS A - WHERE A.name = 'tSQLtExternalAccessKey' - - EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; -END; -GO - -CREATE PROCEDURE RemoveExternalAccessKeyTests.[test removes tSQLtExternalAccessKey certificate] +CREATE PROCEDURE RemoveExternalAccessKeyTests.[test calls tSQLt.RemoveAssemblyKey] AS BEGIN - EXEC RemoveExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.RemoveAssemblyKey'; - SELECT @cmd = 'CREATE ASYMMETRIC KEY tSQLtExternalAccessKey WITH ALGORITHM = RSA_2048 ENCRYPTION BY PASSWORD = '''+CAST(NEWID() AS NVARCHAR(MAX))+''';'; - EXEC master.sys.sp_executesql @cmd; - EXEC tSQLt.RemoveExternalAccessKey; - SELECT * - INTO #Actual - FROM master.sys.asymmetric_keys AS AK - WHERE AK.name = 'tSQLtExternalAccessKey' - - EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; -END; -GO - -CREATE PROCEDURE RemoveExternalAccessKeyTests.[test removes tSQLtExternalAccessKey login] -AS -BEGIN - EXEC RemoveExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); + SELECT _id_ INTO #Actual FROM tSQLt.RemoveAssemblyKey_SpyProcedureLog; - SELECT @cmd = 'CREATE LOGIN tSQLtExternalAccessKey WITH PASSWORD = '''+CAST(NEWID() AS NVARCHAR(MAX))+''';'; - EXEC master.sys.sp_executesql @cmd; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - EXEC tSQLt.RemoveExternalAccessKey; - - SELECT * - INTO #Actual - FROM master.sys.server_principals AS SP - WHERE SP.name = 'tSQLtExternalAccessKey' - - EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; -END; -GO - -CREATE PROCEDURE RemoveExternalAccessKeyTests.[test removes tSQLtExternalAccessKey certificate and login in correct order] -AS -BEGIN - EXEC RemoveExternalAccessKeyTests.DropExistingItems; - - DECLARE @cmd NVARCHAR(MAX); + INSERT INTO #Expected VALUES (1); - SELECT @cmd = 'CREATE ASYMMETRIC KEY tSQLtExternalAccessKey WITH ALGORITHM = RSA_2048 ENCRYPTION BY PASSWORD = '''+CAST(NEWID() AS NVARCHAR(MAX))+''';'; - EXEC master.sys.sp_executesql @cmd; - SELECT @cmd = 'CREATE LOGIN tSQLtExternalAccessKey FROM ASYMMETRIC KEY tSQLtExternalAccessKey;'; - EXEC master.sys.sp_executesql @cmd; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; - EXEC tSQLt.RemoveExternalAccessKey; - - SELECT * - INTO #Actual - FROM - ( - SELECT AK.name - FROM master.sys.asymmetric_keys AS AK - WHERE AK.name = 'tSQLtExternalAccessKey' - UNION ALL - SELECT SP.name - FROM master.sys.server_principals AS SP - WHERE SP.name = 'tSQLtExternalAccessKey' - )A - EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; END; GO -CREATE PROCEDURE RemoveExternalAccessKeyTests.[test non-sysadmin cannot execute procedure] +CREATE PROCEDURE RemoveExternalAccessKeyTests.[test prints sensible deprecation warning] AS BEGIN - DECLARE @cmd NVARCHAR(MAX); - - SET @cmd = 'CREATE LOGIN RemoveExternalAccessKeyTestsUser1 WITH PASSWORD=''(*&^#@($&^%(&@#^$%(!&@'';'; - EXEC master.sys.sp_executesql @cmd; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.RemoveAssemblyKey'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; - SET @cmd = 'CREATE USER RemoveExternalAccessKeyTestsUser1;ALTER ROLE db_owner ADD MEMBER RemoveExternalAccessKeyTestsUser1;'; - IF((SELECT I.SqlVersion FROM tSQLt.Info() AS I) < 11) - BEGIN - SET @cmd = 'CREATE USER RemoveExternalAccessKeyTestsUser1;EXEC sys.sp_addrolemember @rolename = ''db_owner'', @membername = ''RemoveExternalAccessKeyTestsUser1'';'; - END - EXEC master.sys.sp_executesql @cmd; - EXEC sys.sp_executesql @cmd; - - EXEC tSQLt.ExpectException @ExpectedMessage = 'Only principals with CONTROL SERVER permission can execute this procedure.'; - - EXECUTE AS LOGIN = 'RemoveExternalAccessKeyTestsUser1'; EXEC tSQLt.RemoveExternalAccessKey; - REVERT; - -END; -GO -CREATE PROCEDURE RemoveExternalAccessKeyTests.[test login with control server can execute procedure] -AS -BEGIN - - EXEC RemoveExternalAccessKeyTests.DropExistingItems; + SELECT Message INTO #Actual FROM tSQLt.Private_Print_SpyProcedureLog ; - DECLARE @cmd NVARCHAR(MAX); - - SET @cmd = 'CREATE LOGIN RemoveExternalAccessKeyTestsUser1 WITH PASSWORD=''(*&^#@($&^%(&@#^$%(!&@'';'; - EXEC master.sys.sp_executesql @cmd; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('tSQLt.RemoveExternalAccessKey is deprecated. Please use tSQLt.RemoveAssemblyKey instead.'); - SET @cmd = 'GRANT CONTROL SERVER TO RemoveExternalAccessKeyTestsUser1;'; - EXEC master.sys.sp_executesql @cmd; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; - EXEC tSQLt.ExpectNoException; - - EXECUTE AS LOGIN = 'RemoveExternalAccessKeyTestsUser1'; - EXEC tSQLt.RemoveExternalAccessKey; - REVERT; - END; GO - diff --git a/Tests.SA/Tests.SA.sqlproj b/Tests.SA/Tests.SA.sqlproj new file mode 100644 index 000000000..88aebb1a0 --- /dev/null +++ b/Tests.SA/Tests.SA.sqlproj @@ -0,0 +1,13 @@ + + + + + Tests.SA + {4183AC53-E498-4F50-9B76-2DDB36DBAA83} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + 1033, CI + + + + + \ No newline at end of file diff --git a/Tests.SA/Tests.SA.ssmssqlproj b/Tests.SA/Tests.SA.ssmssqlproj deleted file mode 100644 index d1b9ca24e..000000000 --- a/Tests.SA/Tests.SA.ssmssqlproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - 2016-01-15T14:50:26.3460199-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - InstallExternalAccessKeyTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - RemoveExternalAccessKeyTests.class.sql - - - - - - - - \ No newline at end of file diff --git a/Tests.SA/_ExploratoryTests_SA.class.sql b/Tests.SA/_ExploratoryTests_SA.class.sql new file mode 100644 index 000000000..cd352483e --- /dev/null +++ b/Tests.SA/_ExploratoryTests_SA.class.sql @@ -0,0 +1,31 @@ +EXEC tSQLt.NewTestClass '_ExploratoryTests_SA'; +GO +CREATE PROCEDURE [_ExploratoryTests_SA].[test MSSQL preserves COLLATION when using SELECT INTO across databases] +AS +BEGIN + SELECT + 'Hello World!' COLLATE SQL_Polish_CP1250_CI_AS c1, + 'Hello World!' COLLATE SQL_Latin1_General_CP437_BIN c2, + 'Hello World!' COLLATE Albanian_BIN2 c3 + INTO [_ExploratoryTests_SA].Table1 + + SELECT * INTO tempdb.dbo.Table2 FROM [_ExploratoryTests_SA].Table1 + + SELECT + C.name COLLATE DATABASE_DEFAULT name, + C.collation_name COLLATE DATABASE_DEFAULT collation_name + INTO #Expected + FROM sys.columns C + WHERE C.object_id = OBJECT_ID('[_ExploratoryTests_SA].Table1'); + + SELECT + C.name COLLATE DATABASE_DEFAULT name, + C.collation_name COLLATE DATABASE_DEFAULT collation_name + INTO #Actual + FROM tempdb.sys.columns C + WHERE C.object_id = OBJECT_ID('tempdb.dbo.Table2'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO diff --git a/Tests/AnnotationHostPlatformTests.class.sql b/Tests/AnnotationHostPlatformTests.class.sql new file mode 100644 index 000000000..6a5e4d5bf --- /dev/null +++ b/Tests/AnnotationHostPlatformTests.class.sql @@ -0,0 +1,72 @@ +EXEC tSQLt.NewTestClass 'AnnotationHostPlatformTests'; +GO +CREATE PROCEDURE AnnotationHostPlatformTests.[test allows test to execute if actual host platform is equal to the provided one] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:RunOnlyOnHostPlatform](''SomePlatform'') +CREATE PROCEDURE MyInnerTests.[test should execute] AS RAISERROR(''MM>M>M'',16,10); + '); + + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_HostPlatform'; + EXEC('INSERT INTO tSQLt.Private_HostPlatform(host_platform) VALUES (''SomePlatform'');'); + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should execute]', + @ExpectedMessage = '%MM>M>M%'; +END; +GO +CREATE PROCEDURE AnnotationHostPlatformTests.[test doesn't allow test to execute if actual host platform is not equal to the provided one] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:RunOnlyOnHostPlatform](''AnotherPlatform'') +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_HostPlatform'; + EXEC('INSERT INTO tSQLt.Private_HostPlatform(host_platform) VALUES (''SomePlatform'');'); + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]'; +END; +GO +CREATE PROCEDURE AnnotationHostPlatformTests.[test provides a useful skip reason] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:RunOnlyOnHostPlatform](''AnotherPlatform'') +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_HostPlatform'; + EXEC('INSERT INTO tSQLt.Private_HostPlatform(host_platform) VALUES (''SomePlatform'');'); + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = 'HostPlatform is required to be ''AnotherPlatform'', but is ''SomePlatform''.' +END; +GO +CREATE PROCEDURE AnnotationHostPlatformTests.[test includes current and required HostPlatform in reason message] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:RunOnlyOnHostPlatform](''Platform17'') +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_HostPlatform'; + EXEC('INSERT INTO tSQLt.Private_HostPlatform(host_platform) VALUES (''Platform13'');'); + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = 'HostPlatform is required to be ''Platform17'', but is ''Platform13''.' +END; +GO diff --git a/Tests/AnnotationNoTransactionTests.class.sql b/Tests/AnnotationNoTransactionTests.class.sql new file mode 100644 index 000000000..798b7748b --- /dev/null +++ b/Tests/AnnotationNoTransactionTests.class.sql @@ -0,0 +1,1581 @@ +EXEC tSQLt.NewTestClass 'AnnotationNoTransactionTests'; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test runs test without transaction] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS INSERT INTO #TranCount VALUES(''I'',@@TRANCOUNT);; + '); + + SELECT 'B' Id, @@TRANCOUNT TranCount + INTO #TranCount; + + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + + INSERT INTO #TranCount VALUES('A',@@TRANCOUNT); + + SELECT Id, TranCount-MIN(TranCount)OVER() AdjustedTrancount + INTO #Actual + FROM #TranCount; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('B',0),('I',0),('A',0); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test transaction name is NULL in TestResults table] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS RETURN; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + SELECT TranName INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES(NULL); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test if not NoTransaction TranName is valued in TestResults table] +AS +BEGIN + DECLARE @ActualTranName NVARCHAR(MAX); + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE MyInnerTests.[test should execute inside of transaction] AS RETURN;'); + + EXEC tSQLt.Run 'MyInnerTests.[test should execute inside of transaction]'; + SET @ActualTranName = (SELECT TranName FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = 'tSQLtTran%', @Actual = @ActualTranName; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test succeeding test gets correct entry in TestResults table] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS RETURN; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + SELECT Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('Success'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test failing test gets correct entry in TestResults table] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS EXEC tSQLt.Fail ''Some Obscure Reason''; + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + SELECT Result, Msg INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('Failure','Some Obscure Reason'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION AnnotationNoTransactionTests.[Return 42424242 prefix before ERROR_MESSAGE()]() +RETURNS TABLE +AS +RETURN + SELECT '42424242:'+ERROR_MESSAGE() FormattedError; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test recoverable erroring test gets correct entry in TestResults table] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetFormattedErrorInfo', @FakeFunctionName = 'AnnotationNoTransactionTests.[Return 42424242 prefix before ERROR_MESSAGE()]'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS RAISERROR (''Some Obscure Recoverable Error'', 16, 10); + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + SELECT Result, Msg INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('Error','42424242:Some Obscure Recoverable Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is executed] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS RETURN; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT FullTestName INTO #Actual FROM tSQLt.Private_CleanUp_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('[MyInnerTests].[test1]'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is not called if test is not annotated and passing] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE MyInnerTests.[test1] AS RETURN;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT * INTO #Actual FROM tSQLt.Private_CleanUp_SpyProcedureLog; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is not called if test is not annotated and failing] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE MyInnerTests.[test1] AS EXEC tSQLt.Fail;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT * INTO #Actual FROM tSQLt.Private_CleanUp_SpyProcedureLog; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is not called if test is not annotated and erroring] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE MyInnerTests.[test1] AS RAISERROR(''X'',16,10);'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT * INTO #Actual FROM tSQLt.Private_CleanUp_SpyProcedureLog; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp error message is appended to tSQLt.TestResult.Msg] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS PRINT 1/0; + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'SET @ErrorMsg = '''';'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = '% ', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is called before the test result message is printed] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS RAISERROR('''',16,10); + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'SET @ErrorMsg = '''';'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Message FROM tSQLt.Private_Print_SpyProcedureLog WHERE Message LIKE '%%'); + + EXEC tSQLt.AssertLike @ExpectedPattern = '% ', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt tables are backed up before test is executed] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual SELECT Action FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + SELECT Action INTO #Actual FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('Save'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test using SkipTest and NoTransaction annotation skips the test] +AS +BEGIN + CREATE TABLE #SkippedTestExecutionLog (Id INT); + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + --[@'+'tSQLt:SkipTest]('') + CREATE PROCEDURE MyInnerTests.[skippedTest] + AS + BEGIN + INSERT INTO #SkippedTestExecutionLog VALUES (1); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[skippedTest]'; + + EXEC tSQLt.AssertEmptyTable @TableName = '#SkippedTestExecutionLog'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test does not call 'Save' if @NoTransactionFlag=0;] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual SELECT Action FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + SELECT TOP(0) Action INTO #Actual FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test does not call tSQLt.Private_NoTransactionHandleTables if @NoTransactionFlag=1 and @SkipTestFlag=1] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + --[@'+'tSQLt:SkipTest]('''') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + EXEC tSQLt.AssertEmptyTable @TableName = 'tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[Redact IsTestObject status on all objects] +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + WITH MarkedTestDoubles AS + ( + SELECT + TempO.Name, + SCHEMA_NAME(TempO.schema_id) SchemaName, + TempO.type ObjectType + FROM sys.tables TempO + JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = TempO.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + ) + SELECT @cmd = + ( + SELECT + 'EXEC sp_updateextendedproperty ''tSQLt.IsTempObject'',-1342,''SCHEMA'', '''+MTD.SchemaName+''', ''TABLE'', '''+MTD.Name+''';' + FROM MarkedTestDoubles MTD + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); +END; +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[Restore IsTestObject status on all objects] +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + WITH MarkedTestDoubles AS + ( + SELECT + TempO.Name, + SCHEMA_NAME(TempO.schema_id) SchemaName, + TempO.type ObjectType + FROM sys.tables TempO + JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = TempO.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = -1342 + ) + SELECT @cmd = + ( + SELECT + 'EXEC sp_updateextendedproperty ''tSQLt.IsTempObject'',1,''SCHEMA'', '''+MTD.SchemaName+''', ''TABLE'', '''+MTD.Name+''';' + FROM MarkedTestDoubles MTD + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); +END; +GO +CREATE FUNCTION AnnotationNoTransactionTests.PassThrough(@TestName NVARCHAR(MAX)) +RETURNS TABLE +AS +RETURN + SELECT @TestName TestName +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[CLEANUP: test an unrecoverable erroring test gets correct (Success/Failure but not Error) entry in TestResults table] +AS +BEGIN + EXEC tSQLt.SetSummaryError 1; + EXEC tSQLt.DropClass MyInnerTests; + --EXEC tSQLt.UndoTestDoubles; + --ROLLBACK +END; +GO + +--[@tSQLt:NoTransaction]('AnnotationNoTransactionTests.[CLEANUP: test an unrecoverable erroring test gets correct (Success/Failure but not Error) entry in TestResults table]') +/* This test must be NoTransaction because the inner test will invalidate any open transaction causing chaos and turmoil in the reactor. */ +CREATE PROCEDURE AnnotationNoTransactionTests.[test an unrecoverable erroring test gets correct entry in TestResults table] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetLastTestNameIfNotProvided', @FakeFunctionName = 'AnnotationNoTransactionTests.PassThrough'; /* --<-- Prevent tSQLt-internal turmoil */ + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_SaveTestNameForSession';/* --<-- Prevent tSQLt-internal turmoil */ + EXEC ('CREATE SCHEMA MyInnerTests AUTHORIZATION [tSQLt.TestClass];'); + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should cause unrecoverable error] AS PRINT CAST(''Some obscure string'' AS INT); + '); + + /*******************************************************************************************************************************/ + /************************* MESSING WITH THIS CODE WILL PUT tSQLt INTO AN INVALID STATE! ****************************************/ + /**/CREATE TABLE #CleanUpProcedures_StopExecutionForInnerTests(I INT); + /**/EXEC tSQLt.SpyProcedure + /**/ @ProcedureName = 'tSQLt.Private_CleanUp', + /**/ @CommandToExecute = 'IF(OBJECT_ID(''tempdb..#CleanUpProcedures_StopExecutionForInnerTests'')IS NOT NULL)BEGIN RETURN;END;', + /**/ @CallOriginal = 1; + /**/EXEC tSQLt.SpyProcedure + /**/ @ProcedureName = 'tSQLt.Private_AssertNoSideEffects', + /**/ @CommandToExecute = 'IF(OBJECT_ID(''tempdb..#CleanUpProcedures_StopExecutionForInnerTests'')IS NOT NULL)BEGIN RETURN;END;', + /**/ @CallOriginal = 1; + /************************* MESSING WITH THIS CODE WILL PUT tSQLt INTO AN INVALID STATE! ****************************************/ + /*******************************************************************************************************************************/ + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test should cause unrecoverable error]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT ISNULL(Result,'')+'<><><>'+ISNULL(Msg,'') FROM tSQLt.TestResult WHERE Name = '[MyInnerTests].[test should cause unrecoverable error]'); + + EXEC tSQLt.AssertLike @ExpectedPattern = 'Error<><><>%Conversion failed when converting the varchar value ''Some obscure string'' to data type int.%', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed after test completes] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp1''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'UserCleanUp1'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed even if it has a single quote in its name and/or its schema name] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInner''Tests' + EXEC(' + CREATE PROCEDURE [MyInner''Tests].[UserClean''Up1] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserClean''''Up1''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInner''''Tests].[UserClean''''Up1]'') + CREATE PROCEDURE [MyInner''Tests].[test''1] + AS + BEGIN + RETURN + END; + '); + + CREATE TABLE #Actual (col1 NVARCHAR(MAX)); + + + EXEC tSQLt.Run '[MyInner''Tests].[test''1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('UserClean''Up1'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test annotation throws appropriate error if specified Test-CleanUp does not exist] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUpDoesNotExist]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN + END; + '); + + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, + TR.Result, + TR.Msg + INTO #Actual + FROM tSQLt.TestResult AS TR + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES ( + '[MyInnerTests].[test1]', + 'Error', + 'There is a problem with this annotation: [@tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUpDoesNotExist]'') +Original Error: {16,10;(null)} Test CleanUp Procedure [MyInnerTests].[UserCleanUpDoesNotExist] does not exist or is not a procedure.') + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test annotation throws appropriate error if specified Test-CleanUp is not a procedure] + +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE VIEW [MyInnerTests].[NotAProcedure] AS SELECT 1 X;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[NotAProcedure]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult AS TR); + EXEC tSQLt.AssertLike @ExpectedPattern = '%Test CleanUp Procedure [[]MyInnerTests].[[]NotAProcedure] does not exist or is not a procedure.', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test annotation does not throw error if specified Test-CleanUp is a CLR stored procedure] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](''tSQLt_testutil.AClrSsp'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN + END; + '); + EXEC tSQLt.SetSummaryError @SummaryError = 1; + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'--, @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt executes multiple Test-CleanUp in the order they are specified] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp7] AS BEGIN INSERT INTO #Actual VALUES (''CleanUp7''); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp3] AS BEGIN INSERT INTO #Actual VALUES (''CleanUp3''); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp9] AS BEGIN INSERT INTO #Actual VALUES (''CleanUp9''); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUp7'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUp3'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUp9'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'CleanUp7'),(3, 'CleanUp3'), (4, 'CleanUp9'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed after Test-CleanUp] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[TestCleanUp] AS BEGIN INSERT INTO #Actual VALUES (''TestCleanUp''); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp] AS BEGIN INSERT INTO #Actual VALUES (''(Schema)CleanUp''); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''MyInnerTests.TestCleanUp'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'TestCleanUp'),(3, '(Schema)CleanUp'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed only if it is a stored procedure] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE VIEW [MyInnerTests].[CleanUp] AS SELECT 1 X;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed if schema name contains single quote] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInner''Tests' + EXEC('CREATE PROCEDURE [MyInner''Tests].[CleanUp] AS BEGIN INSERT INTO #Actual VALUES (''[MyInner''''Tests].[CleanUp]''); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInner''Tests].[test1] + AS + BEGIN + RETURN; + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + EXEC tSQLt.Run '[MyInner''Tests].[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, '[MyInner''Tests].[CleanUp]'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed even if name is differently cased] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[clEaNuP] AS BEGIN INSERT INTO #Actual VALUES (''[MyInnerTests].[clEaNuP]''); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + EXEC tSQLt.Run '[MyInnerTests].[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, '[MyInnerTests].[clEaNuP]'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error causes test result to be Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION AnnotationNoTransactionTests.[return 42134213 if correct error]() +RETURNS TABLE +AS +RETURN + SELECT '42134213' FormattedError WHERE ERROR_MESSAGE() = 'This is an error ;)'; +GO +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION AnnotationNoTransactionTests.[return 42424242+@NewMessage, @NewResult]( + @PrevMessage NVARCHAR(MAX), + @PrevResult NVARCHAR(MAX), + @NewMessage NVARCHAR(MAX), + @NewResult NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT '42424242:'+@NewMessage Message, @NewResult Result +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error causes an appropriate message to be written to the tSQLt.TestResult table] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetFormattedErrorInfo', @FakeFunctionName = 'AnnotationNoTransactionTests.[return 42134213 if correct error]'; + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_HandleMessageAndResult', @FakeFunctionName = 'AnnotationNoTransactionTests.[return 42424242+@NewMessage, @NewResult]'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Msg, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('42424242:Error during clean up: (42134213)','Error'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO + +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test appends message to any test error if Schema-CleanUp errors] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is a CleanUp error ;)'',15,12); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RAISERROR(''This is a Test error ;)'',16,10); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @FriendlyMsg NVARCHAR(MAX) = (SELECT TR.Msg FROM tSQLt.TestResult AS TR); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%This is a Test error ;)% || %This is a CleanUp error ;)%', @Actual = @FriendlyMsg; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed even if the test errors] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp1''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'UserCleanUp1'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error causes failing test to be set to Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + EXEC tSQLt.Fail; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error causes passing test to be set to Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error and test error still results in Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RAISERROR(''Some random error!'', 16, 10); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test appends message to any test error if Test-CleanUp errors] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[TestCleanUp] + AS + BEGIN + RAISERROR(''This is a CleanUp error ;)'',15,12); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[TestCleanUp]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RAISERROR(''This is a Test error ;)'',16,10); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @FriendlyMsg NVARCHAR(MAX) = (SELECT TR.Msg FROM tSQLt.TestResult AS TR); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%This is a Test error ;)% || %This is a CleanUp error ;)%', @Actual = @FriendlyMsg; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed if previous Test-CleanUp errors] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp1''); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp2] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp2''); + RAISERROR(''some error in UserCleanUp2'',16,10); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp3] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp3''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp2]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp3]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + + EXEC tSQLt.Run 'MyInnerTests.[test1]', 'tSQLt.NullTestResultFormatter'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'UserCleanUp1'), (3, 'UserCleanUp2'), (4, 'UserCleanUp3'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp appends all individual error messages] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''some error in Schema-CleanUp'',16,10); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + RAISERROR(''some error in UserCleanUp1'',16,10); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp2] + AS + BEGIN + RAISERROR(''some error in UserCleanUp2'',16,10); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp3] + AS + BEGIN + RAISERROR(''some error in UserCleanUp3'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp2]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp3]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + EXEC tSQLt.Fail ''MyInnerTests.test1 has failed.''; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', 'tSQLt.NullTestResultFormatter'; + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%MyInnerTests.test1%UserCleanUp1%UserCleanUp2%UserCleanUp3%Schema-CleanUp%', @Actual = @Actual; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp error causes failing test to be set to Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[TestCleanUp1] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[TestCleanUp1]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + EXEC tSQLt.Fail; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp error causes passing test to be set to Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[TestCleanUp1] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[TestCleanUp1]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp error and test error still results in Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[TestCleanUp1] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[TestCleanUp1]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RAISERROR(''test error'',16,10); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp error causes an appropriate message to be written to tSQLt.TestResult even if ERROR_PROCEDURE is null] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyOtherInnerTests' + EXEC(' + CREATE PROCEDURE [MyOtherInnerTests].[TestCleanUp1] + AS + BEGIN + /*wasting lines...*/ + EXEC(''RAISERROR(''''This is another error ;)'''',15,12)''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyOtherInnerTests].[TestCleanUp1]'') + CREATE PROCEDURE [MyOtherInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyOtherInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @FriendlyMsg NVARCHAR(MAX) = (SELECT TR.Msg FROM tSQLt.TestResult AS TR); + EXEC tSQLt.AssertLike @ExpectedPattern = 'Error during clean up: (%This is another error ;)%Procedure: %)', @Actual = @FriendlyMsg; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Private-CleanUp error stops execution of all subsequent tests] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS INSERT INTO #Actual DEFAULT VALUES; + '); + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test2] AS INSERT INTO #Actual DEFAULT VALUES; + '); + CREATE TABLE #Actual(Id CHAR(1) DEFAULT '*'); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'SET @Result = ''Abort''; SET @ErrorMsg = ''Error in Private_CleanUp'';'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + BEGIN TRY + EXEC tSQLt.Run 'MyInnerTests'; + END TRY + BEGIN CATCH + /*Not interested in the specific error here.*/ + END CATCH; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('*'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Private_CleanUp @Result OUTPUT gets written to tSQLt.TestResult before tSQLt stops] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS RETURN; + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'SET @Result = ''V1234'';'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + + EXEC tSQLt.Run 'MyInnerTests'; + + SELECT Result INTO #Actual FROM tSQLt.TestResult + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('V1234'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed through tSQLt.Private_CleanUpCmdHandler] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp] AS BEGIN RETURN; END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler'; + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT _id_, CleanUpCmd INTO #Actual FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog + WHERE(NOT EXISTS(SELECT 1 FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog WHERE CleanUpCmd LIKE '%MyInnerTests%CleanUp%')); + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual', @Message = 'Expected a call for MyInnerTests.Cleanup1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed through tSQLt.Private_CleanUpCmdHandler] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[Test-CleanUp1] AS BEGIN RETURN; END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[Test-CleanUp2] AS BEGIN RETURN; END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[Test-CleanUp3] AS BEGIN RETURN; END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp1]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp2]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp3]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler'; + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT _id_, CleanUpCmd INTO #Actual FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog + WHERE(NOT EXISTS(SELECT 1 FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog WHERE CleanUpCmd LIKE '%MyInnerTests%Test-CleanUp1%')) + OR(NOT EXISTS(SELECT 1 FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog WHERE CleanUpCmd LIKE '%MyInnerTests%Test-CleanUp2%')) + OR(NOT EXISTS(SELECT 1 FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog WHERE CleanUpCmd LIKE '%MyInnerTests%Test-CleanUp3%')); + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual', @Message = 'Expected a call for MyInnerTests.Test-Cleanup(s)'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed in the order specified and again at the end] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUpA] AS BEGIN INSERT INTO #Actual VALUES(OBJECT_NAME(@@PROCID)); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUpB] AS BEGIN INSERT INTO #Actual VALUES(OBJECT_NAME(@@PROCID)); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp] AS BEGIN INSERT INTO #Actual VALUES(OBJECT_NAME(@@PROCID)); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUpA'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUp'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUpB'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + CREATE TABLE #Actual(OrderNumber INT IDENTITY(1,1), CleanUpName NVARCHAR(MAX) ); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(1,'CleanUpA'),(2,'CleanUp'),(3,'CleanUpB'),(4,'CleanUp'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed multiple times if it is specified multiple times] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[Test-CleanUp1] AS BEGIN INSERT INTO #Actual DEFAULT VALUES; END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp1]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp1]'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.[Test-CleanUp1]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + CREATE TABLE #Actual(WasCalled BIT DEFAULT 1); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(1),(1),(1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test any CleanUp (Test, Schema, or Private) that alters the test result adds the previous result to the error message] +AS +BEGIN + DECLARE @TestMessage NVARCHAR(MAX) = 'BeforeMessage'; + + EXEC tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd='RAISERROR(''NewMessage'',16,10)', + @TestMsg = @TestMessage OUT, + @TestResult = 'BeforeResult' , + @ResultInCaseOfError = 'NewResult'; + + EXEC tSQLt.AssertLike @ExpectedPattern = 'BeforeMessage [[]Result: BeforeResult] || %NewMessage%', @Actual = @TestMessage; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test any CleanUp adds the previous result to the error message even if the previous result is NULL] +AS +BEGIN + DECLARE @TestMessage NVARCHAR(MAX) = 'BeforeMessage'; + + EXEC tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd='RAISERROR(''NewMessage'',16,10)', + @TestMsg = @TestMessage OUT, + @TestResult = NULL , + @ResultInCaseOfError = 'NewResult'; + + EXEC tSQLt.AssertLike @ExpectedPattern = 'BeforeMessage [[]Result: ] || %NewMessage%', @Actual = @TestMessage; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Cleanup is executed after the outer-most test execution try catch and before writing to TestResult] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'IF(@Action = ''Save'')RAISERROR(''Error caught in outermost try-catch'', 16, 10);'; + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + RAISERROR(''Error in UserCleanUp1'', 16, 10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.SetSummaryError @SummaryError = 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%Error caught in outermost try-catch%Error in UserCleanUp1%', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Private_AssertNoSideEffects is executed after all CleanUps and throws an error if there are new/missing/renamed objects] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC ('CREATE TABLE MyInnerTests.TestTable1 (Id INT);'); + EXEC ('CREATE TABLE MyInnerTests.TestTable2 (Id INT);'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'DROP TABLE MyInnerTests.TestTable1; CREATE TABLE MyInnerTests.TestTable1 (Id INT); EXEC sys.sp_rename ''MyInnerTests.TestTable2'', ''TestTable3'';'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%Added%TestTable1%Added%TestTable3%Deleted%TestTable1%Deleted%TestTable2%', @Actual = @Actual; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Private-CleanUp when @Result = FATAL, it is not overwritten by another Result] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'IF(@Action=''Reset'')RAISERROR(''AFatalError'',16,10);'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles', @CommandToExecute = 'RAISERROR(''AnAbortError'',16,10);'; + + EXEC tSQLt.SetSummaryError 0; + BEGIN TRY + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + END TRY + BEGIN CATCH + /*-- not really interested in this error --*/ + END CATCH; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Result FROM tSQLt.TestResult); + + EXEC tSQLt.AssertEqualsString @Expected = 'FATAL', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_RunTest_TestExecution is not called if SkipTest & NoTransaction] +AS +BEGIN + --EXEC tSQLt.Fail 'This test has gone horribly wrong and kills tSQLt.'; + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + --[@'+'tSQLt:SkipTest](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + DECLARE @Actual TABLE(_id_ INT, TestName NVARCHAR(MAX));/*-- @TableVariables persist regardless of transaction rollbacks --*/ + DECLARE @TranName CHAR(32);EXEC tSQLt.GetNewTranName @TranName=@TranName OUT; + SAVE TRAN @TranName; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RunTest_TestExecution', @CallOriginal=1; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; /*--<-- only needed if test fails, so we get a clearer fail message --<--*/ + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + INSERT INTO @Actual SELECT _id_,TestName FROM tSQLt.Private_RunTest_TestExecution_SpyProcedureLog; + ROLLBACK TRAN @TranName; /*-- Rolling back just the last 5 lines (though, not the insert into @Actual). --*/ + + SELECT * INTO #Actual FROM @Actual; + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/AnnotationSkipTestTests.class.sql b/Tests/AnnotationSkipTestTests.class.sql new file mode 100644 index 000000000..0a803d436 --- /dev/null +++ b/Tests/AnnotationSkipTestTests.class.sql @@ -0,0 +1,194 @@ +EXEC tSQLt.NewTestClass 'AnnotationSkipTestTests'; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test skips the test] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:SkipTest]('''') +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]'; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test inserts @SkipReason message into tSQLt.TestResult] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:SkipTest](''AnImportantMessage'') +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = 'AnImportantMessage'; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test can handle ' in @SkipReason] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:SkipTest](''M''''essag''''e'') +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = 'M''essag''e'; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test skips when @SkipReason = ''] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:SkipTest]('''') +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = ''; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test skips when @SkipReason IS NULL] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:SkipTest](NULL) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = ''; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test skips when @SkipReason is given as DEFAULT] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:SkipTest](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = ''; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test tSQLt.TestCaseSummary() includes single skipped test in count] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + INSERT tSQLt.TestResult(Result) VALUES('Skipped'); + DECLARE @ActualMsg NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestCaseSummary()); + EXEC tSQLt.AssertLike @ExpectedPattern = '%, 1 skipped,%', @Actual = @ActualMsg; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test tSQLt.TestCaseSummary() includes several skipped tests in count] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + INSERT tSQLt.TestResult(Result) VALUES('Skipped'),('Skipped'),('Skipped'); + DECLARE @ActualMsg NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestCaseSummary()); + EXEC tSQLt.AssertLike @ExpectedPattern = '%, 3 skipped,%', @Actual = @ActualMsg; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test tSQLt.TestCaseSummary() correctly reports 0 skipped tests in count] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + INSERT tSQLt.TestResult(Result) VALUES('Success'),('Error'),('Failure'); + DECLARE @ActualMsg NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestCaseSummary()); + EXEC tSQLt.AssertLike @ExpectedPattern = '%, 0 skipped,%', @Actual = @ActualMsg; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test tSQLt.TestCaseSummary() returns skipped count] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + INSERT tSQLt.TestResult(Result) VALUES('Success'),('Error'),('Failure'); + INSERT tSQLt.TestResult(Result) VALUES('Skipped'),('Skipped'),('Skipped'); + SELECT SkippedCnt, Cnt INTO #Actual FROM tSQLt.TestCaseSummary(); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(3,7); +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test TestEndTime IS NOT NULL if the test is skipped] +AS +BEGIN + EXEC tSQLt.NewTestClass 'InnerTests'; + EXEC( + '--[@'+'tSQLt:SkipTest]('''') + CREATE PROC InnerTests.[test Me] AS RAISERROR(''Test should not execute'',16,10);' + ); + DECLARE @RunTestCmd NVARCHAR(MAX) = 'EXEC tSQLt.Run @TestName = ''InnerTests.[test Me]'', @TestResultFormatter = ''tSQLt.NullTestResultFormatter'';'; + + EXEC(@RunTestCmd); + + DECLARE @ActualTestEndTime DATETIME2 = (SELECT TR.TestEndTime FROM tSQLt.TestResult AS TR WHERE TR.Name = '[InnerTests].[test Me]'); + + EXEC tSQLt.AssertNotEquals @Expected = NULL, @Actual = @ActualTestEndTime; +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test annotation listed after SkipTest are not processed] +AS +BEGIN + EXEC tSQLt.NewTestClass 'InnerTests'; + EXEC( + '--[@'+'tSQLt:SkipTest]('''') + --[@'+'tSQLt:AnAnnotation]() + CREATE PROC InnerTests.[test Me] AS RAISERROR(''test should not execute'',16,10);' + ); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:AnAnnotation]() RETURNS TABLE AS RETURN SELECT ''RAISERROR(''''this annotation should not execute'''',16,10);'' [AnnotationCmd];'); + + EXEC tSQLt.SetSummaryError @SummaryError=1; + + DECLARE @RunTestCmd NVARCHAR(MAX) = 'EXEC tSQLt.Run @TestName = ''InnerTests.[test Me]'', @TestResultFormatter = ''tSQLt.DefaultResultFormatter'';'; + + EXEC tSQLt.ExpectNoException; + EXEC(@RunTestCmd); +END; +GO +CREATE PROCEDURE AnnotationSkipTestTests.[test annotations are only processed until SkipTest is encountered] +AS +BEGIN + EXEC tSQLt.NewTestClass 'InnerTests'; + EXEC( + ' + --[@'+'tSQLt:AnAnnotation](1) + --[@'+'tSQLt:AnAnnotation](2) + --[@'+'tSQLt:AnAnnotation](3) + --[@'+'tSQLt:SkipTest]('''') + --[@'+'tSQLt:AnAnnotation](4) + --[@'+'tSQLt:AnAnnotation](5) + --[@'+'tSQLt:AnAnnotation](6) + CREATE PROC InnerTests.[test Me] AS RAISERROR(''test should not execute'',16,10);' + ); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:AnAnnotation](@id INT) RETURNS TABLE AS RETURN SELECT ''PRINT ''''AnnotationExecutedWith''+CAST(@id AS NVARCHAR(MAX))+'''''';'' [AnnotationCmd];'); + + DECLARE @RunTestCmd NVARCHAR(MAX) = 'EXEC tSQLt.Run @TestName = ''InnerTests.[test Me]'', @TestResultFormatter = ''tSQLt.NullTestResultFormatter'';'; + + EXEC tSQLt.CaptureOutput @RunTestCmd; + + DECLARE @ActualOutput NVARCHAR(MAX); + SELECT @ActualOutput = OutputText + FROM tSQLt.CaptureOutputLog; + + IF( + NOT(@ActualOutput LIKE '%AnnotationExecutedWith1%AnnotationExecutedWith2%AnnotationExecutedWith3%') + OR @ActualOutput LIKE '%AnnotationExecutedWith4%' + OR @ActualOutput LIKE '%AnnotationExecutedWith5%' + OR @ActualOutput LIKE '%AnnotationExecutedWith6%' + ) + BEGIN + EXEC tSQLt.Fail 'there was a problem with executing the correct annotations. Captured output:',@ActualOutput; + END; +END; \ No newline at end of file diff --git a/Tests/AnnotationSqlServerVersionTests.class.sql b/Tests/AnnotationSqlServerVersionTests.class.sql new file mode 100644 index 000000000..6faa12a79 --- /dev/null +++ b/Tests/AnnotationSqlServerVersionTests.class.sql @@ -0,0 +1,212 @@ +EXEC tSQLt.NewTestClass 'AnnotationSqlServerVersionTests'; +GO +CREATE FUNCTION AnnotationSqlServerVersionTests.[42.17.1986.57]() +RETURNS TABLE +AS +RETURN SELECT CAST(N'42.17.1986.57' AS NVARCHAR(128)) AS ProductVersion, 'My Edition' AS Edition, NULL AS HostPlatform; +GO +CREATE FUNCTION AnnotationSqlServerVersionTests.[13.0.1986.57]() +RETURNS TABLE +AS +RETURN SELECT CAST(N'13.0.1986.57' AS NVARCHAR(128)) AS ProductVersion, 'My Edition' AS Edition, NULL AS HostPlatform; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MinSqlMajorVersion allows test to execute if actual version is larger] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MinSqlMajorVersion](13) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''<><><> test executed <><><>'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = '%<><><> test executed <><><>%'; +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MinSqlMajorVersion doesn't allow test to execute if actual version is smaller] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MinSqlMajorVersion](43) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]' +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MinSqlMajorVersion doesn't allow test to execute if actual version is way smaller] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MinSqlMajorVersion](93) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]' +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MinSqlMajorVersion allows test to execute if actual version is equal] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MinSqlMajorVersion](42) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''<><><> test executed <><><>'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = '%<><><> test executed <><><>%'; +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MinSqlMajorVersion provides a useful skip reason] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MinSqlMajorVersion](43) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = 'Minimum required version is 43, but current version is 42.' +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MinSqlMajorVersion includes current and required min version in reason message] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MinSqlMajorVersion](17) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[13.0.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = 'Minimum required version is 17, but current version is 13.' +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MaxSqlMajorVersion allows test to execute if actual version is smaller] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MaxSqlMajorVersion](43) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''<><><> test executed! <><><>'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = '%<><><> test executed! <><><>%'; +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MaxSqlMajorVersion doesn't allow test to execute if actual version is larger] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MaxSqlMajorVersion](41) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]' +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MaxSqlMajorVersion doesn't allow test to execute if actual version is way larger] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MaxSqlMajorVersion](13) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]' +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MaxSqlMajorVersion allows test to execute if actual version is equal] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MaxSqlMajorVersion](42) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''<><><> test executed <><><>'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = '%<><><> test executed <><><>%'; +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MaxSqlMajorVersion provides a useful skip reason] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MaxSqlMajorVersion](41) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = 'Maximum allowed version is 41, but current version is 42.' +END; +GO +CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MaxSqlMajorVersion includes current and required max version in reason message] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MaxSqlMajorVersion](11) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[13.0.1986.57]'; + + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage = 'Maximum allowed version is 11, but current version is 13.' +END; +GO diff --git a/Tests/AnnotationsTests.class.sql b/Tests/AnnotationsTests.class.sql new file mode 100644 index 000000000..581289af2 --- /dev/null +++ b/Tests/AnnotationsTests.class.sql @@ -0,0 +1,416 @@ +EXEC tSQLt.NewTestClass 'AnnotationsTests'; +GO +CREATE PROCEDURE AnnotationsTests.[test a test runs if single annotation indicates to run] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation]() +CREATE PROCEDURE MyInnerTests.[test will execute] AS EXEC tSQLt.Fail ''test executed''; + '); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT NULL [AnnotationCmd];'); + BEGIN TRY + EXEC tSQLt.Run 'MyInnerTests'; + END TRY + BEGIN CATCH + -- intentionally empty + END CATCH; + SELECT TestCase,Msg INTO #Actual FROM tSQLt.TestResult; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('test will execute','test executed'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test a test is skipped if single annotation indicates not to run] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE PROCEDURE tSQLt.[@'+'tSQLt:MyTestAnnotationProc] AS INSERT INTO #SkipTest DEFAULT VALUES;'); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@'+'tSQLt:MyTestAnnotationProc]'' [AnnotationCmd];'); + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]'; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test a test is skipped if another single annotation indicates not to run] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + /* +--[@'+'tSQLt:ADifferentAnnotation]() +*/ +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE PROCEDURE tSQLt.[@'+'tSQLt:MyTestAnnotationProc] AS INSERT INTO #SkipTest DEFAULT VALUES;'); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:ADifferentAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@'+'tSQLt:MyTestAnnotationProc]'' [AnnotationCmd];'); + + EXEC tSQLt_testutil.AssertTestSkipped + @TestName = 'MyInnerTests.[test should not execute]'; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test errors test with appropriate message if it encounters a nonexistent annotation] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:ANonexistentAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS EXEC tSQLt.Fail ''test executed''; + '); + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage='There is a problem with the annotations:%'; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test errors test with appropriate message if a parameter is missing] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:A1ParameterAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS EXEC tSQLt.Fail ''test executed''; + '); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:A1ParameterAnnotation](@P1 INT) RETURNS TABLE AS RETURN SELECT NULL [AnnotationCmd];'); + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage='%@tSQLt:A1ParameterAnnotation%'; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test errors test with appropriate message if a parameter is superfluous] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:A1ParameterAnnotation](3,42) +CREATE PROCEDURE MyInnerTests.[test should not execute] AS EXEC tSQLt.Fail ''test executed''; + '); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:A1ParameterAnnotation](@P1 INT) RETURNS TABLE AS RETURN SELECT NULL [AnnotationCmd];'); + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage='%@tSQLt:A1ParameterAnnotation%'; + +END; +GO +CREATE PROCEDURE AnnotationsTests.[test specifies details if there's an internal error in the annotation function] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:AnErroringAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS EXEC tSQLt.Fail ''test executed''; + '); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:AnErroringAnnotation]() RETURNS TABLE AS RETURN SELECT 1/0 [AnnotationCmd];'); + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage='%{16,10} There is an internal error for annotation: [[]@tSQLt:AnErroringAnnotation]() + caused by {16,1} Divide by zero error encountered.%'; + +END; +GO +CREATE PROCEDURE AnnotationsTests.[test annotation error includes severity, and state] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + DECLARE @Cmd NVARCHAR(MAX) = ' +--[@'+CAST(0x7400530051004C0074003A0041006E0041006E006E006F0074006100740069006F006E005D0028002900200041003B0052004100490053004500520052004F005200280027007800780027002C00310035002C00390029003B004400450043004C00410052004500200040007300200049004E0054003B002000530045004C00450043005400200054004F0050002800300029002000400073003D006F0062006A006500630074005F00690064002000660072006F006D0020007300790073002E006F0062006A006500630074007300 AS NVARCHAR(MAX))+' +CREATE PROCEDURE MyInnerTests.[test should not execute] AS EXEC tSQLt.Fail ''test executed'';'; + EXEC(@Cmd); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:AnAnnotation]() RETURNS TABLE AS RETURN SELECT NULL [AnnotationCmd];'); + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage='%{15,9}%'; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test can handle string as parameter] +AS +BEGIN + DECLARE @AnnotationName NVARCHAR(MAX) = '[@tSQLt:AStringParameterAnnotation]'; + DECLARE @FullAnnotationName NVARCHAR(MAX) = 'tSQLt.'+@AnnotationName; + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--'+@AnnotationName+'(''SomeRandomString'') +CREATE PROCEDURE MyInnerTests.[test nothing] AS RETURN; + '); + EXEC('CREATE FUNCTION '+@FullAnnotationName+' (@P1 NVARCHAR(MAX))RETURNS TABLE AS RETURN SELECT ''RAISERROR(''''[%s]'''',16,10,''''''+@P1+'''''');'' AnnotationCmd;'); + + EXEC tSQLt_testutil.AssertTestErrors @TestName = 'MyInnerTests.[test nothing]', @ExpectedMessage='%[[]SomeRandomString]%'; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test AnnotationCommand is executed] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''RAISERROR(''''AnnotationCommand executed.'''',16,10);'' [AnnotationCmd];'); + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage='%AnnotationCommand executed.%'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](13) +CREATE PROCEDURE AnnotationsTests.[test erroring AnnotationCommand produces helpful error message (Pre 2017)] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE PROCEDURE tSQLt.[@tSQLt:MyAnnotationHelper] AS RAISERROR(''AnnotationCommand executed.'',15,9);'); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:MyAnnotationHelper];'' [AnnotationCmd];'); + + DECLARE @ExpectedMessage NVARCHAR(MAX) = + 'There is a problem with this annotation: [[]@tSQLt:MyTestAnnotation]()'+CHAR(13)+CHAR(10)+ + 'Original Error: {15,9;@tSQLt:MyAnnotationHelper} AnnotationCommand executed.' + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage=@ExpectedMessage; +END; +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE AnnotationsTests.[test erroring AnnotationCommand produces helpful error message (2017 or later)] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE PROCEDURE tSQLt.[@tSQLt:MyAnnotationHelper] AS RAISERROR(''AnnotationCommand executed.'',15,9);'); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:MyAnnotationHelper];'' [AnnotationCmd];'); + + DECLARE @ExpectedMessage NVARCHAR(MAX) = + 'There is a problem with this annotation: [[]@tSQLt:MyTestAnnotation]()'+CHAR(13)+CHAR(10)+ + 'Original Error: {15,9;tSQLt.@tSQLt:MyAnnotationHelper} AnnotationCommand executed.' + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage=@ExpectedMessage; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test Syntax Error produces helpful error message] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE PROCEDURE tSQLt.[@tSQLt:MyAnnotationHelper] AS EXEC(''Very Intentional Syntax_Error'');'); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:MyAnnotationHelper];'' [AnnotationCmd];'); + + DECLARE @ExpectedMessage NVARCHAR(MAX) = + '%Incorrect syntax near ''Syntax_Error''.' + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage=@ExpectedMessage; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test 1/0 produces helpful error message] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE PROCEDURE tSQLt.[@tSQLt:MyAnnotationHelper] AS PRINT 1/0;'); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:MyAnnotationHelper];'' [AnnotationCmd];'); + + DECLARE @ExpectedMessage NVARCHAR(MAX) = + '%Divide by zero error encountered.' + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage=@ExpectedMessage; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test nonexistingcolumn produces helpful error message] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE PROCEDURE tSQLt.[@tSQLt:MyAnnotationHelper] AS EXEC(''SELECT nonexistingcolumn FROM (VALUES(NULL))A(A);'');'); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:MyAnnotationHelper];'' [AnnotationCmd];'); + + DECLARE @ExpectedMessage NVARCHAR(MAX) = + '%Invalid column name ''nonexistingcolumn''.' + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage=@ExpectedMessage; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test can handle quotes in AnnotationCmd] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''RAISERROR(''''got''''''''here'''',16,10);'' [AnnotationCmd];'); + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage='%got''here%'; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test identifies the annotation that causes the error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation1]() +--[@'+'tSQLt:MyTestAnnotation2]() +--[@'+'tSQLt:MyTestAnnotation3]() +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); + '); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation1]() RETURNS TABLE AS RETURN SELECT ''DECLARE @i1 INT;'' [AnnotationCmd];'); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation2]() RETURNS TABLE AS RETURN SELECT ''DECLARE @i2 INT = 1/0;'' [AnnotationCmd];'); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation3]() RETURNS TABLE AS RETURN SELECT ''DECLARE @i3 INT;'' [AnnotationCmd];'); + + EXEC tSQLt_testutil.AssertTestErrors + @TestName = 'MyInnerTests.[test should not execute]', + @ExpectedMessage='%[[]@tSQLt:MyTestAnnotation2]()%'; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test TestStartTime is captured before annotations are processed] +AS +BEGIN + + EXEC tSQLt.NewTestClass 'InnerTests'; + EXEC( + '--[@'+'tSQLt:AnAnnotation]() + CREATE PROC InnerTests.[test Me] AS RETURN;' + ); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:AnAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt_testutil.WaitForMS 120;'' [AnnotationCmd];'); + + DECLARE @RunTestCmd NVARCHAR(MAX) = 'EXEC tSQLt.Run @TestName = ''InnerTests.[test Me]'', @TestResultFormatter = ''tSQLt.NullTestResultFormatter'';'; + EXEC tSQLt.CaptureOutput @command= @RunTestCmd; + + DECLARE @actual DATETIME2; + DECLARE @after DATETIME2; + DECLARE @before DATETIME2; + DECLARE @expectedIntervalStart DATETIME2; + DECLARE @expectedIntervalEnd DATETIME2; + + SET @before = SYSDATETIME(); + + EXEC(@RunTestCmd); + + SET @after = SYSDATETIME(); + + SELECT @actual = TestStartTime + FROM tSQLt.TestResult AS TR + + + SELECT + @expectedIntervalStart = @before, + @expectedIntervalEnd = DATEADD(MILLISECOND, -120, @after); + + DECLARE @msg NVARCHAR(MAX); + IF(@actual < @expectedIntervalStart OR @actual > @expectedIntervalEnd OR @actual IS NULL) + BEGIN + SET @msg = + 'Expected:'+ + CONVERT(NVARCHAR(MAX),@expectedIntervalStart,121)+ + ' <= '+ + ISNULL(CONVERT(NVARCHAR(MAX),@actual,121),'!NULL!')+ + ' <= '+ + CONVERT(NVARCHAR(MAX),@expectedIntervalEnd,121); + EXEC tSQLt.Fail @msg; + END; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test TestEndTime is captured after annotations are processed] +AS +BEGIN + + EXEC tSQLt.NewTestClass 'InnerTests'; + EXEC( + '--[@'+'tSQLt:AnAnnotation]() + CREATE PROC InnerTests.[test Me] AS RETURN;' + ); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:AnAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt_testutil.WaitForMS 120;'' [AnnotationCmd];'); + + DECLARE @RunTestCmd NVARCHAR(MAX) = 'EXEC tSQLt.Run @TestName = ''InnerTests.[test Me]'', @TestResultFormatter = ''tSQLt.NullTestResultFormatter'';'; + EXEC tSQLt.CaptureOutput @command= @RunTestCmd; + + DECLARE @actual DATETIME2; + DECLARE @after DATETIME2; + DECLARE @before DATETIME2; + DECLARE @expectedIntervalStart DATETIME2; + DECLARE @expectedIntervalEnd DATETIME2; + + SET @before = SYSDATETIME(); + + EXEC(@RunTestCmd); + + SET @after = SYSDATETIME(); + + SELECT @actual = TestEndTime + FROM tSQLt.TestResult AS TR + + SELECT + @expectedIntervalStart = DATEADD(MILLISECOND,120,@before), + @expectedIntervalEnd = @after; + + DECLARE @msg NVARCHAR(MAX); + IF(@actual < @expectedIntervalStart OR @actual > @expectedIntervalEnd OR @actual IS NULL) + BEGIN + SET @msg = + 'Expected:'+ + CONVERT(NVARCHAR(MAX),@expectedIntervalStart,121)+ + ' <= '+ + ISNULL(CONVERT(NVARCHAR(MAX),@actual,121),'!NULL!')+ + ' <= '+ + CONVERT(NVARCHAR(MAX),@expectedIntervalEnd,121); + EXEC tSQLt.Fail @msg; + END; +END; +GO +CREATE PROCEDURE AnnotationsTests.[test Executes Annotations before test, no matter where they are located in test] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:MyTestAnnotation](1) +CREATE PROCEDURE MyInnerTests.[test inner test] +--[@'+'tSQLt:MyTestAnnotation](2) +AS +--[@'+'tSQLt:MyTestAnnotation](3) +EXEC tSQLt.AssertEqualsTable ''#Expected'',''#Actual''; +--[@'+'tSQLt:MyTestAnnotation](4) + '); + EXEC('CREATE FUNCTION tSQLt.[@'+'tSQLt:MyTestAnnotation](@id INT) RETURNS TABLE AS RETURN SELECT ''INSERT INTO #Actual VALUES(''+CAST(@id AS NVARCHAR(MAX))+'');'' [AnnotationCmd];'); + + CREATE TABLE #Actual(id INT); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1),(2),(3),(4); + + EXEC tSQLt.SetSummaryError @SummaryError = 1; + EXEC tSQLt.ExpectNoException; --<-- Assertion happens within [test inner test] + EXEC tSQLt.Run @TestName = 'MyInnerTests.[test inner test]', + @TestResultFormatter = 'tSQLt.DefaultResultFormatter'; +END; + diff --git a/Tests/ApplyConstraintTests.class.sql b/Tests/ApplyConstraintTests.class.sql index 780da383e..bf96d1c45 100644 --- a/Tests/ApplyConstraintTests.class.sql +++ b/Tests/ApplyConstraintTests.class.sql @@ -844,6 +844,50 @@ BEGIN END; GO +CREATE PROC ApplyConstraintTests.[test ApplyConstraint can handle data types with length when changing NULLability] +AS +BEGIN + EXEC('CREATE SCHEMA schemaA;'); + CREATE TABLE schemaA.tableA ( + Length1 VARCHAR(42) NOT NULL, + CONSTRAINT testConstraint PRIMARY KEY (Length1) + ); + + EXEC tSQLt.FakeTable 'schemaA.tableA'; + + EXEC tSQLt.ExpectNoException; + INSERT INTO schemaA.tableA(Length1)VALUES(NULL); + TRUNCATE TABLE schemaA.tableA; + + EXEC tSQLt.ApplyConstraint 'schemaA.tableA', 'testConstraint'; + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Cannot insert the value NULL%Length1%'; + INSERT INTO schemaA.tableA(Length1)VALUES(NULL); +END; +GO + +CREATE PROC ApplyConstraintTests.[test ApplyConstraint can handle data types with precision and scale when changing NULLability] +AS +BEGIN + EXEC('CREATE SCHEMA schemaA;'); + CREATE TABLE schemaA.tableA ( + Precision_Scale NUMERIC(21,3) NOT NULL, + CONSTRAINT testConstraint PRIMARY KEY (Precision_Scale) + ); + + EXEC tSQLt.FakeTable 'schemaA.tableA'; + + EXEC tSQLt.ExpectNoException; + INSERT INTO schemaA.tableA(Precision_Scale)VALUES(NULL); + TRUNCATE TABLE schemaA.tableA; + + EXEC tSQLt.ApplyConstraint 'schemaA.tableA', 'testConstraint'; + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Cannot insert the value NULL%Precision_Scale%'; + INSERT INTO schemaA.tableA(Precision_Scale)VALUES(NULL); +END; +GO + CREATE PROC ApplyConstraintTests.[test ApplyConstraint applies multi-column unique constraint] AS BEGIN @@ -1006,4 +1050,103 @@ BEGIN END; GO +CREATE PROC ApplyConstraintTests.[test ApplyConstraint calls tSQLt.Private_MarktSQLtTempObject on new check constraints] +AS +BEGIN + CREATE TABLE ApplyConstraintTests.aSimpleTable ( Id INT CONSTRAINT aSimpleTableConstraint CHECK(Id > 0)); + DECLARE @OriginalObjectId INT = OBJECT_ID('ApplyConstraintTests.aSimpleTableConstraint'); + + EXEC tSQLt.FakeTable @TableName = 'ApplyConstraintTests.aSimpleTable'; + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + TRUNCATE TABLE tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog;--Quirkiness of testing the framework that you use to run the test + EXEC tSQLt.ApplyConstraint @TableName = 'ApplyConstraintTests.aSimpleTable', @ConstraintName = 'aSimpleTableConstraint'; + + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[ApplyConstraintTests].[aSimpleTableConstraint]', N'CONSTRAINT', OBJECT_NAME(@OriginalObjectId)); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROC ApplyConstraintTests.[test ApplyConstraint calls tSQLt.Private_MarktSQLtTempObject on new primary key] +AS +BEGIN + CREATE TABLE ApplyConstraintTests.aSimpleTable ( Id INT CONSTRAINT aSimpleTableConstraint PRIMARY KEY ); + DECLARE @OriginalObjectId INT = OBJECT_ID('ApplyConstraintTests.aSimpleTableConstraint'); + + EXEC tSQLt.FakeTable @TableName = 'ApplyConstraintTests.aSimpleTable'; + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + TRUNCATE TABLE tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog;--Quirkiness of testing the framework that you use to run the test + + EXEC tSQLt.ApplyConstraint @TableName = 'ApplyConstraintTests.aSimpleTable', @ConstraintName = 'aSimpleTableConstraint'; + + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[ApplyConstraintTests].[aSimpleTableConstraint]', N'CONSTRAINT', OBJECT_NAME(@OriginalObjectId)); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROC ApplyConstraintTests.[test ApplyConstraint calls tSQLt.Private_MarktSQLtTempObject on new unique key] +AS +BEGIN + CREATE TABLE ApplyConstraintTests.aSimpleTable ( Id INT CONSTRAINT aSimpleTableConstraint UNIQUE ); + DECLARE @OriginalObjectId INT = OBJECT_ID('ApplyConstraintTests.aSimpleTableConstraint'); + + EXEC tSQLt.FakeTable @TableName = 'ApplyConstraintTests.aSimpleTable'; + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + TRUNCATE TABLE tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog;--Quirkiness of testing the framework that you use to run the test + + EXEC tSQLt.ApplyConstraint @TableName = 'ApplyConstraintTests.aSimpleTable', @ConstraintName = 'aSimpleTableConstraint'; + + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[ApplyConstraintTests].[aSimpleTableConstraint]', N'CONSTRAINT', OBJECT_NAME(@OriginalObjectId)); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROC ApplyConstraintTests.[test ApplyConstraint calls tSQLt.Private_MarktSQLtTempObject on new foreign key] +AS +BEGIN + CREATE TABLE ApplyConstraintTests.aForeignTable ( Id INT PRIMARY KEY ); + CREATE TABLE ApplyConstraintTests.aSimpleTable ( Id INT, fId INT CONSTRAINT aSimpleTableConstraint FOREIGN KEY REFERENCES ApplyConstraintTests.aForeignTable (Id) ); + DECLARE @OriginalObjectId INT = OBJECT_ID('ApplyConstraintTests.aSimpleTableConstraint'); + + EXEC tSQLt.FakeTable @TableName = 'ApplyConstraintTests.aSimpleTable'; + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + TRUNCATE TABLE tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog;--Quirkiness of testing the framework that you use to run the test + + EXEC tSQLt.ApplyConstraint @TableName = 'ApplyConstraintTests.aSimpleTable', @ConstraintName = 'aSimpleTableConstraint'; + + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[ApplyConstraintTests].[aSimpleTableConstraint]', N'CONSTRAINT', OBJECT_NAME(@OriginalObjectId)); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO diff --git a/Tests/ApplyTriggerTests.class.sql b/Tests/ApplyTriggerTests.class.sql index 26fadad3b..933309560 100644 --- a/Tests/ApplyTriggerTests.class.sql +++ b/Tests/ApplyTriggerTests.class.sql @@ -120,3 +120,29 @@ BEGIN END; GO +CREATE PROC ApplyTriggerTests.[test ApplyTrigger calls tSQLt.Private_MarktSQLtTempObject on new check constraints] +AS +BEGIN + CREATE TABLE ApplyTriggerTests.aSimpleTable ( Id INT); + EXEC('CREATE TRIGGER ApplyTriggerTests.aSimpleTrigger ON ApplyTriggerTests.aSimpleTable FOR INSERT AS RETURN;'); + DECLARE @OriginalObjectId INT = OBJECT_ID('ApplyTriggerTests.aSimpleTrigger'); + + EXEC tSQLt.FakeTable @TableName = 'ApplyTriggerTests.aSimpleTable'; + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + TRUNCATE TABLE tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog;--Quirkiness of testing the framework that you use to run the test + + EXEC tSQLt.ApplyTrigger @TableName = 'ApplyTriggerTests.aSimpleTable', @TriggerName = 'aSimpleTrigger'; + + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[ApplyTriggerTests].[aSimpleTrigger]', N'TRIGGER', OBJECT_NAME(@OriginalObjectId)); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO diff --git a/Tests/AssertEqualsTableSchemaTests.class.sql b/Tests/AssertEqualsTableSchemaTests.class.sql index 71d79aac5..4215285c3 100644 --- a/Tests/AssertEqualsTableSchemaTests.class.sql +++ b/Tests/AssertEqualsTableSchemaTests.class.sql @@ -190,7 +190,7 @@ BEGIN EXEC tSQLt_testutil.assertFailCalled @Command, 'AssertEqualsTableSchema did not call Fail'; END; GO -CREATE PROCEDURE AssertEqualsTableSchemaTests.[test fail if 2nd table has Column with different colation order] +CREATE PROCEDURE AssertEqualsTableSchemaTests.[test fail if 2nd table has Column with different collation order] AS BEGIN CREATE TABLE AssertEqualsTableSchemaTests.Tbl1( @@ -288,6 +288,23 @@ BEGIN EXEC tSQLt.AssertEqualsTableSchema @Expected = 'AssertEqualsTableSchemaTests.Tbl1', @Actual = 'AssertEqualsTableSchemaTests.Tbl2'; END; GO +--[@tSQLt:SkipTest]('Not currently supported. See Issue https://github.com/tSQLt-org/tSQLt/issues/119') +CREATE PROCEDURE AssertEqualsTableSchemaTests.[test calls fail if tables are #temporary and their schema does not match] +AS +BEGIN + CREATE TABLE #Actual( + Id INT PRIMARY KEY, + NoKey INT NULL + ); + CREATE TABLE #Expected( + Id BIT PRIMARY KEY, + NoKey NVARCHAR(MAX) NULL + ); + DECLARE @Command VARCHAR(MAX) = 'EXEC tSQLt.AssertEqualsTableSchema @Expected = ''#Expected'', @Actual = ''#Actual'';'; + EXEC tSQLt_testutil.assertFailCalled @Command, 'AssertEqualsTableSchema did not call Fail'; + +END; +GO /* SELECT diff --git a/Tests/AssertEqualsTableTests.class.sql b/Tests/AssertEqualsTableTests.class.sql index 83f0a4a89..08fb5b046 100644 --- a/Tests/AssertEqualsTableTests.class.sql +++ b/Tests/AssertEqualsTableTests.class.sql @@ -468,11 +468,10 @@ GO CREATE PROCEDURE AssertEqualsTableTests.[test considers NULL values identical] AS BEGIN - SELECT * - INTO AssertEqualsTableTests.NullCellTableCopy - FROM tSQLt.Private_NullCellTable; + SELECT NULL [aNULLColumn] INTO #Actual; + SELECT NULL [aNULLColumn] INTO #Expected; - EXEC tSQLt.AssertEqualsTable 'tSQLt.Private_NullCellTable', 'AssertEqualsTableTests.NullCellTableCopy'; + EXEC tSQLt.AssertEqualsTable #Expected, #Actual; END; GO @@ -705,7 +704,6 @@ BEGIN EXEC tSQLt.AssertEqualsTable 'expected', 'actual'; END; GO - CREATE PROC AssertEqualsTableTests.test_AssertEqualsTable_works_with_expected_having_identity_column AS BEGIN @@ -760,4 +758,100 @@ BEGIN EXEC tSQLt.AssertEqualsTable 'expected', 'actual'; END; -GO \ No newline at end of file +GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertEqualsTableTests.[test can handle 2008 date data types] +AS +BEGIN + EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'DATE', '''2012-01-01'',''2012-06-19'',''2012-10-25'''; + EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'TIME', '''10:10:10'',''11:11:11'',''12:12:12'''; + EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'DATETIMEOFFSET', '''2012-01-01 10:10:10.101010 +10:10'',''2012-06-19 11:11:11.111111 +11:11'',''2012-10-25 12:12:12.121212 -12:12'''; + EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'DATETIME2', '''2012-01-01 10:10:10.101010'',''2012-06-19 11:11:11.111111'',''2012-10-25 12:12:12.121212'''; +END; +GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertEqualsTableTests.[test can handle hierarchyid data type] +AS +BEGIN + EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can handle a datatype] 'HIERARCHYID', '''/10/'',''/11/'',''/12/'''; +END; +GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertEqualsTableTests.[test all unsupported 2008 data types] +AS +BEGIN + EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can NOT handle a datatype] 'GEOMETRY', 'geometry::STPointFromText(''POINT (10 10)'', 0),geometry::STPointFromText(''POINT (11 11)'', 0),geometry::STPointFromText(''POINT (12 12)'', 0)'; + EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can NOT handle a datatype] 'GEOGRAPHY', 'geography::STGeomFromText(''LINESTRING(-10.10 10.10, -50.10 50.10)'', 4326),geography::STGeomFromText(''LINESTRING(-11.11 11.11, -50.11 50.11)'', 4326),geography::STGeomFromText(''LINESTRING(-12.12 12.12, -50.12 50.12)'', 4326)'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC AssertEqualsTableTests.[test RC table is marked as tSQLt.IsTempObject] +AS +BEGIN + CREATE TABLE #Table1 (id INT); + CREATE TABLE #Table2 (id INT); + + SELECT name, + object_id, + schema_id + INTO #TableListBefore + FROM sys.tables + WHERE name LIKE 'tSQLt[_]tempobject[_]%'; + + EXEC tSQLt.AssertEqualsTable '#Table1','#Table2'; + + SELECT QUOTENAME(SCHEMA_NAME(NewTable.schema_id))+'.'+QUOTENAME(NewTable.name) TableName, EP.value AS [tSQLt.IsTempObject] + INTO #Actual + FROM( + SELECT name, object_id, schema_id FROM sys.tables WHERE name LIKE 'tSQLt[_]tempobject[_]%' + EXCEPT + SELECT name, object_id, schema_id FROM #TableListBefore AS TLB + ) NewTable + LEFT JOIN sys.extended_properties EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.name = 'tSQLt.IsTempObject' + AND NewTable.object_id = EP.major_id + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected SELECT TableName, 1 FROM #Actual; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC AssertEqualsTableTests.[test RC table is created in the tSQLt schema] +AS +BEGIN + CREATE TABLE #Table1 (id INT); + CREATE TABLE #Table2 (id INT); + + SELECT name, + object_id, + schema_id + INTO #TableListBefore + FROM sys.tables + WHERE name LIKE 'tSQLt[_]tempobject[_]%'; + + EXEC tSQLt.AssertEqualsTable '#Table1','#Table2'; + + SELECT SCHEMA_NAME(NewTable.schema_id) SchemaName, NewTable.name TableName + INTO #Actual + FROM( + SELECT name, object_id, schema_id FROM sys.tables WHERE name LIKE 'tSQLt[_]tempobject[_]%' + EXCEPT + SELECT name, object_id, schema_id FROM #TableListBefore AS TLB + ) NewTable + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected SELECT 'tSQLt', TableName FROM #Actual; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + diff --git a/Tests/tSQLt_test_AssertResultSetsHaveSameMetaData.class.sql b/Tests/AssertResultSetsHaveSameMetaDataTests.class.sql similarity index 94% rename from Tests/tSQLt_test_AssertResultSetsHaveSameMetaData.class.sql rename to Tests/AssertResultSetsHaveSameMetaDataTests.class.sql index e0f0177cd..e62472991 100644 --- a/Tests/tSQLt_test_AssertResultSetsHaveSameMetaData.class.sql +++ b/Tests/AssertResultSetsHaveSameMetaDataTests.class.sql @@ -48,10 +48,10 @@ BEGIN END; GO -CREATE PROC tSQLt_test_AssertResultSetsHaveSameMetaData.[xtest AssertResultSetsHaveSameMetaData fails when one result set has no rows for versions before SQL Server 2012] +--[@tSQLt:SkipTest]('INTENTIONALLY DISABLED UNTIL WE FIGURE OUT WHY IT SOMETIMES FAILS AND SOMETIMES PASSES') +CREATE PROC tSQLt_test_AssertResultSetsHaveSameMetaData.[test AssertResultSetsHaveSameMetaData fails when one result set has no rows for versions before SQL Server 2012] AS BEGIN - -- INTENTIONALLY DISABLED UNTIL WE FIGURE OUT WHY IT SOMETIMES FAILS AND SOMETIMES PASSES IF (SELECT CAST(SUBSTRING(product_version, 1, CHARINDEX('.', product_version) - 1) AS INT) FROM sys.dm_os_loaded_modules WHERE name LIKE '%\sqlservr.exe') >= 11 BEGIN EXEC tSQLt.AssertResultSetsHaveSameMetaData diff --git a/Tests.2008/AssertStringInTests_2008.class.sql b/Tests/AssertStringInTests.class.sql similarity index 80% rename from Tests.2008/AssertStringInTests_2008.class.sql rename to Tests/AssertStringInTests.class.sql index 0b9fbb9ed..aea7f8b8e 100644 --- a/Tests.2008/AssertStringInTests_2008.class.sql +++ b/Tests/AssertStringInTests.class.sql @@ -1,6 +1,7 @@ -EXEC tSQLt.NewTestClass 'AssertStringInTests_2008'; +EXEC tSQLt.NewTestClass 'AssertStringInTests'; GO -CREATE PROCEDURE AssertStringInTests_2008.[test fails if set is empty] +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertStringInTests.[test fails if set is empty] AS BEGIN @@ -11,7 +12,8 @@ BEGIN @Actual = ''Some String'';'; END; GO -CREATE PROCEDURE AssertStringInTests_2008.[test succeeds if value is the only element] +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertStringInTests.[test succeeds if value is the only element] AS BEGIN @@ -24,7 +26,8 @@ BEGIN END; GO -CREATE PROCEDURE AssertStringInTests_2008.[test succeeds if value is one of many elements] +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertStringInTests.[test succeeds if value is one of many elements] AS BEGIN @@ -37,7 +40,8 @@ BEGIN END; GO -CREATE PROCEDURE AssertStringInTests_2008.[test includes string and ordered table in fail message] +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertStringInTests.[test includes string and ordered table in fail message] AS BEGIN CREATE TABLE #ExpectedSet (value NVARCHAR(MAX)); @@ -65,7 +69,8 @@ BEGIN EXEC tSQLt.AssertEqualsString @Expected = @ExpectedMessage, @Actual = @FailMessage; END; GO -CREATE PROCEDURE AssertStringInTests_2008.[test produces adequate failure message if @Actual = 'NULL'] +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertStringInTests.[test produces adequate failure message if @Actual = 'NULL'] AS BEGIN CREATE TABLE #ExpectedSet (value NVARCHAR(MAX)); @@ -93,7 +98,8 @@ BEGIN EXEC tSQLt.AssertEqualsString @Expected = @ExpectedMessage, @Actual = @FailMessage; END; GO -CREATE PROCEDURE AssertStringInTests_2008.[test produces adequate failure message if @Expected is empty] +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertStringInTests.[test produces adequate failure message if @Expected is empty] AS BEGIN DECLARE @FailMessage NVARCHAR(MAX); @@ -118,7 +124,8 @@ BEGIN EXEC tSQLt.AssertEqualsString @Expected = @ExpectedMessage, @Actual = @FailMessage; END; GO -CREATE PROCEDURE AssertStringInTests_2008.[test produces adequate failure message if @Expected is empty and @Actual is NULL] +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertStringInTests.[test produces adequate failure message if @Expected is empty and @Actual is NULL] AS BEGIN DECLARE @FailMessage NVARCHAR(MAX); @@ -143,7 +150,8 @@ BEGIN EXEC tSQLt.AssertEqualsString @Expected = @ExpectedMessage, @Actual = @FailMessage; END; GO -CREATE PROC AssertStringInTests_2008.[test AssertStringIn passes supplied message before original failure message when calling fail] +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE AssertStringInTests.[test AssertStringIn passes supplied message before original failure message when calling fail] AS BEGIN EXEC tSQLt_testutil.AssertFailMessageLike @@ -157,6 +165,3 @@ BEGIN '{MyMessage}%NULL%value%'; END; GO - - ---message \ No newline at end of file diff --git a/Tests/BootStrapTest.sql b/Tests/BootStrapTest.sql index 7209c67f9..1f8953ec0 100644 --- a/Tests/BootStrapTest.sql +++ b/Tests/BootStrapTest.sql @@ -49,7 +49,7 @@ BEGIN CATCH END CATCH; BEGIN TRY - IF NOT EXISTS(SELECT 1 FROM tSQLt.TestResult WHERE Msg LIKE 'TestCase was executed! (42)%') + IF NOT EXISTS(SELECT 1 FROM tSQLt.TestResult WHERE Msg LIKE '%TestCase was executed! (42)%') RAISERROR('TestCase was not executed',16,10); PRINT 'Test passed'; @@ -307,7 +307,7 @@ BEGIN CATCH RAISERROR(@m,0,1); END CATCH; -DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 1 test case(s) executed, 1 succeeded, 0 failed, 0 errored.'; +DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 1 test case(s) executed, 1 succeeded, 0 skipped, 0 failed, 0 errored.'; DECLARE @actual VARCHAR(MAX); SELECT @actual = Message @@ -350,7 +350,7 @@ BEGIN CATCH RAISERROR(@m,0,1); END CATCH; -DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 1 test case(s) executed, 0 succeeded, 1 failed, 0 errored.'; +DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 1 test case(s) executed, 0 succeeded, 0 skipped, 1 failed, 0 errored.'; DECLARE @actual VARCHAR(MAX); SELECT @actual = Message @@ -393,7 +393,7 @@ BEGIN CATCH RAISERROR(@m,0,1); END CATCH; -DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 1 test case(s) executed, 0 succeeded, 0 failed, 1 errored.'; +DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 1 test case(s) executed, 0 succeeded, 0 skipped, 0 failed, 1 errored.'; DECLARE @actual VARCHAR(MAX); SELECT @actual = Message @@ -438,7 +438,7 @@ BEGIN CATCH RAISERROR(@m,0,1); END CATCH; -DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 2 test case(s) executed, 2 succeeded, 0 failed, 0 errored.'; +DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 2 test case(s) executed, 2 succeeded, 0 skipped, 0 failed, 0 errored.'; DECLARE @actual VARCHAR(MAX); SELECT @actual = Message @@ -482,7 +482,7 @@ BEGIN CATCH RAISERROR(@m,0,1); END CATCH; -DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 2 test case(s) executed, 1 succeeded, 1 failed, 0 errored.'; +DECLARE @expected VARCHAR(MAX); SET @expected = 'Test Case Summary: 2 test case(s) executed, 1 succeeded, 0 skipped, 1 failed, 0 errored.'; DECLARE @actual VARCHAR(MAX); SELECT @actual = Message diff --git a/Tests/DropClassTests.class.sql b/Tests/DropClassTests.class.sql index 473adf02e..4d4005976 100644 --- a/Tests/DropClassTests.class.sql +++ b/Tests/DropClassTests.class.sql @@ -40,13 +40,15 @@ BEGIN END END; GO -CREATE PROC DropClassTests.[test removes UDDTs after tables] +CREATE PROC DropClassTests.[test removes UDDTs after the objects that use them] AS BEGIN EXEC('CREATE SCHEMA MyTestClass;'); EXEC('CREATE TYPE MyTestClass.UDT FROM INT;'); EXEC('CREATE TABLE MyTestClass.tbl(i MyTestClass.UDT);'); + EXEC('CREATE PROCEDURE MyTestClass.ssp @i MyTestClass.UDT AS RETURN;'); + EXEC('CREATE FUNCTION MyTestClass.[IF](@i MyTestClass.UDT) RETURNS TABLE AS RETURN SELECT 0 X;'); EXEC tSQLt.ExpectNoException; @@ -107,5 +109,228 @@ BEGIN END; GO +CREATE PROC DropClassTests.[test removes class if it contains a CLR TVF] +AS +BEGIN + EXEC('CREATE SCHEMA MyTestClass;'); + + EXEC(' +CREATE FUNCTION MyTestClass.AClrTvf(@p1 NVARCHAR(MAX), @p2 NVARCHAR(MAX)) + RETURNS TABLE(id INT, val NVARCHAR(MAX)) + AS EXTERNAL NAME tSQLtTestUtilCLR.[tSQLtTestUtilCLR.ClrFunctions].AClrTvf; + '); + + EXEC tSQLt.ExpectNoException; + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes SSPs] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE PROC MyTestClass.P AS RETURN;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes VIEWs] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE VIEW MyTestClass.V AS SELECT 0 X;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes CLR SSPs] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE PROC MyTestClass.CLRProcedure @expectedCommand NVARCHAR(MAX), @actualCommand NVARCHAR(MAX) AS EXTERNAL NAME tSQLtCLR.[tSQLtCLR.StoredProcedures].AssertResultSetsHaveSameMetaData;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes Tables] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE TABLE MyTestClass.U(i INT);'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes Inline Table-Valued Functions] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE FUNCTION MyTestClass.[IF]() RETURNS TABLE AS RETURN SELECT 0 X;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes Multi-Statement Table-Valued Functions] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE FUNCTION MyTestClass.[TF]() RETURNS @T TABLE (i INT) BEGIN RETURN; END;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes Scalar Functions] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE FUNCTION MyTestClass.[FN]() RETURNS INT BEGIN RETURN 0; END;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO + +CREATE PROC DropClassTests.[test removes tables referencing each other] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE TABLE MyTestClass.TableA(I INT PRIMARY KEY);'); + EXEC('CREATE TABLE MyTestClass.TableB(I INT PRIMARY KEY REFERENCES MyTestClass.TableA(I));'); + EXEC('ALTER TABLE MyTestClass.TableA ADD FOREIGN KEY (I) REFERENCES MyTestClass.TableB(I);'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROC DropClassTests.[test removes tables referencing each other with names that require quoting ( .')] +AS +BEGIN + + EXEC('CREATE SCHEMA [My.Tes''t Class];'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le A](I INT PRIMARY KEY);'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le B](I INT PRIMARY KEY CONSTRAINT [FK: Ta.b''le B] REFERENCES [My.Tes''t Class].[Ta.b''le A](I));'); + EXEC('ALTER TABLE [My.Tes''t Class].[Ta.b''le A] ADD CONSTRAINT [FK: Ta.b''le A] FOREIGN KEY (I) REFERENCES [My.Tes''t Class].[Ta.b''le B](I);'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'My.Tes''t Class'; + + IF(SCHEMA_ID('My.Tes''t Class') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop [My.Tes''t Class]'; + END +END; +GO +CREATE PROC DropClassTests.[test drop class works if schema name is passed in unquoted] +AS +BEGIN + + EXEC('CREATE SCHEMA [My.Tes''t Class];'); + EXEC('CREATE TYPE [My.Tes''t Class].UDT FROM INT;'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le A](I INT PRIMARY KEY, OO [My.Tes''t Class].UDT);'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le B](I INT PRIMARY KEY CONSTRAINT [FK: Ta.b''le B] REFERENCES [My.Tes''t Class].[Ta.b''le A](I));'); + EXEC('CREATE XML SCHEMA COLLECTION [My.Tes''t Class].TestXMLSchema + AS'''';'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'My.Tes''t Class'; + + IF(SCHEMA_ID('My.Tes''t Class') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop [My.Tes''t Class]'; + END +END; +GO +CREATE PROC DropClassTests.[test drop class works if schema name is passed in quoted] +AS +BEGIN + + EXEC('CREATE SCHEMA [My.Tes''t Class];'); + EXEC('CREATE TYPE [My.Tes''t Class].UDT FROM INT;'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le A](I INT PRIMARY KEY, OO [My.Tes''t Class].UDT);'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le B](I INT PRIMARY KEY CONSTRAINT [FK: Ta.b''le B] REFERENCES [My.Tes''t Class].[Ta.b''le A](I));'); + EXEC('CREATE XML SCHEMA COLLECTION [My.Tes''t Class].TestXMLSchema + AS'''';'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass '[My.Tes''t Class]'; + + IF(SCHEMA_ID('My.Tes''t Class') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop [My.Tes''t Class]'; + END +END; +GO diff --git a/Tests/ExpectExceptionTests.class.sql b/Tests/ExpectExceptionTests.class.sql index bff043c0f..ca07ed2b4 100644 --- a/Tests/ExpectExceptionTests.class.sql +++ b/Tests/ExpectExceptionTests.class.sql @@ -186,7 +186,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyTestClass'; EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectException;EXEC tSQLt.ExpectException;'); - EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','Each test can only contain one call to tSQLt.ExpectException.%'; + EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','%Each test can only contain one call to tSQLt.ExpectException.%'; END; GO CREATE PROCEDURE ExpectExceptionTests.[test expecting error number fails when unexpected error number is used] @@ -287,3 +287,40 @@ BEGIN EXEC tSQLt_testutil.AssertTestFails 'MyTestClass.TestExpectingException'; END; GO + +CREATE PROCEDURE ExpectExceptionTests.[test ExpectException reports TestEndTime when failing] +AS +BEGIN + EXEC tSQLt.NewTestClass 'InnerTests'; + EXEC( + 'CREATE PROC InnerTests.[test Me] AS EXEC tSQLt.ExpectException;' + ); + + DECLARE @RunTestCmd NVARCHAR(MAX) = 'EXEC tSQLt.Run @TestName = ''InnerTests.[test Me]'', @TestResultFormatter = ''tSQLt.NullTestResultFormatter'';'; + + EXEC(@RunTestCmd); + + DECLARE @ActualTestEndTime DATETIME2 = (SELECT TR.TestEndTime FROM tSQLt.TestResult AS TR WHERE TR.Name = '[InnerTests].[test Me]'); + + EXEC tSQLt.AssertNotEquals @Expected = NULL, @Actual = @ActualTestEndTime; +END; +GO + +CREATE PROCEDURE ExpectExceptionTests.[test ExpectException reports TestEndTime when passing] +AS +BEGIN + EXEC tSQLt.NewTestClass 'InnerTests'; + EXEC( + 'CREATE PROC InnerTests.[test Me] AS EXEC tSQLt.ExpectException; RAISERROR(''Expected error'',16,10);' + ); + + DECLARE @RunTestCmd NVARCHAR(MAX) = 'EXEC tSQLt.Run @TestName = ''InnerTests.[test Me]'', @TestResultFormatter = ''tSQLt.NullTestResultFormatter'';'; + + EXEC(@RunTestCmd); + + DECLARE @ActualTestEndTime DATETIME2 = (SELECT TR.TestEndTime FROM tSQLt.TestResult AS TR WHERE TR.Name = '[InnerTests].[test Me]'); + + EXEC tSQLt.AssertNotEquals @Expected = NULL, @Actual = @ActualTestEndTime; + +END; +GO diff --git a/Tests/ExpectNoExceptionTests.class.sql b/Tests/ExpectNoExceptionTests.class.sql index 23e60e6ab..6fb2be2bf 100644 --- a/Tests/ExpectNoExceptionTests.class.sql +++ b/Tests/ExpectNoExceptionTests.class.sql @@ -16,17 +16,29 @@ BEGIN EXEC tSQLt_testutil.AssertTestFails 'MyTestClass.TestExpectingNoException'; END; GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION ExpectNoExceptionTests.[Return 42424242 prefix before ERROR_MESSAGE()]() +RETURNS TABLE +AS +RETURN + SELECT '42424242: '+ERROR_MESSAGE() FormattedError; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO CREATE PROCEDURE ExpectNoExceptionTests.[test tSQLt.ExpectNoException includes error information in fail message ] AS BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetFormattedErrorInfo', @FakeFunctionName = 'ExpectNoExceptionTests.[Return 42424242 prefix before ERROR_MESSAGE()]'; - EXEC tSQLt.NewTestClass 'MyTestClass'; - EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectNoException;RAISERROR(''test error message'',16,10);'); + EXEC tSQLt.NewTestClass 'MyTestClass'; + EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectNoException;RAISERROR(''test error message'',16,10);'); - DECLARE @ExpectedMessage NVARCHAR(MAX); - SET @ExpectedMessage = 'Expected no error to be raised. Instead this error was encountered:'+CHAR(13)+CHAR(10)+ - 'test error message[[]16,10]{TestExpectingNoException,1}'; - EXEC tSQLt_testutil.AssertTestFails 'MyTestClass.TestExpectingNoException', @ExpectedMessage; + DECLARE @ExpectedMessage NVARCHAR(MAX) = + 'Expected no error to be raised. Instead this error was encountered:'+CHAR(13)+CHAR(10)+ + '42424242: test error message'; + + EXEC tSQLt_testutil.AssertTestFails 'MyTestClass.TestExpectingNoException', @ExpectedMessage; END; GO @@ -48,7 +60,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyTestClass'; EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectNoException;EXEC tSQLt.ExpectNoException;'); - EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','Each test can only contain one call to tSQLt.ExpectNoException.%'; + EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','%Each test can only contain one call to tSQLt.ExpectNoException.%'; END; GO CREATE PROCEDURE ExpectNoExceptionTests.[test a ExpectNoException cannot follow an ExpectException] @@ -57,7 +69,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyTestClass'; EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectException;EXEC tSQLt.ExpectNoException;'); - EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','tSQLt.ExpectNoException cannot follow tSQLt.ExpectException inside a single test.%'; + EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','%tSQLt.ExpectNoException cannot follow tSQLt.ExpectException inside a single test.%'; END; GO diff --git a/Tests/FailTests.class.sql b/Tests/FailTests.class.sql index 28aa139d3..3f5bbb828 100644 --- a/Tests/FailTests.class.sql +++ b/Tests/FailTests.class.sql @@ -93,7 +93,7 @@ BEGIN DECLARE @TestResultMessage NVARCHAR(MAX); SELECT @TestResultMessage = Msg - FROM tSQLt.TestMessage; + FROM #TestMessage; DECLARE @ExpectedMessage NVARCHAR(MAX); SET @ExpectedMessage = '%Not really a failure - just seeing that fail works%'+CHAR(13)+CHAR(10)+'Warning: Uncommitable transaction detected!%' diff --git a/Tests/FakeFunctionTests.class.sql b/Tests/FakeFunctionTests.class.sql index f0d883f70..43fde701f 100644 --- a/Tests/FakeFunctionTests.class.sql +++ b/Tests/FakeFunctionTests.class.sql @@ -422,4 +422,365 @@ BEGIN EXEC FakeFunctionTests.[assert parameter missmatch causes error]; END; GO +CREATE PROCEDURE FakeFunctionTests.[test errors when both @FakeFunctionName and @FakeDataSource are passed] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int, @b int) RETURNS TABLE AS RETURN (SELECT @a AS one);'); + EXEC tSQLt.ExpectException @ExpectedMessage = 'Both @FakeFunctionName and @FakeDataSource are valued. Please use only one.', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeFunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'select 1 one'; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test errors when neither @FakeFunctionName nor @FakeDataSource are passed] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int, @b int) RETURNS TABLE AS RETURN (SELECT @a AS one);'); + EXEC tSQLt.ExpectException @ExpectedMessage = 'Either @FakeFunctionName or @FakeDataSource must be provided', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction'; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test errors if function is a SVF and @FakeDataSource is used] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS NVARCHAR(10) AS BEGIN RETURN ''''; END;'); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'You can use @FakeDataSource only with Inline, Multi-Statement or CLR Table-Valued functions.', + @ExpectedSeverity = 16, + @ExpectedState = 10; + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'select 1 as a'; + +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test errors if function is a CLR SVF and @FakeDataSource is used] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS NVARCHAR(10) AS BEGIN RETURN ''''; END;'); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'You can use @FakeDataSource only with Inline, Multi-Statement or CLR Table-Valued functions.', + @ExpectedSeverity = 16, + @ExpectedState = 10; + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'select 1 as a'; + +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake Inline table function using a table as fake data source] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS TABLE AS RETURN (SELECT 1 AS one);'); + + CREATE TABLE FakeFunctionTests.Expected (a CHAR(1)); + INSERT INTO FakeFunctionTests.Expected VALUES('a'); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'FakeFunctionTests.Expected'; + + INSERT INTO FakeFunctionTests.Actual SELECT * FROM FakeFunctionTests.AFunction(); + + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake multi-statement table function using a table as fake data source] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int) RETURNS @t TABLE (a int) AS BEGIN; + INSERT INTO @t (a) VALUES (0) RETURN; END;'); + + CREATE TABLE FakeFunctionTests.Expected (a CHAR(1)); + INSERT INTO FakeFunctionTests.Expected VALUES('a'); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'FakeFunctionTests.Expected'; + + INSERT INTO FakeFunctionTests.Actual SELECT * FROM FakeFunctionTests.AFunction(123); + + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake CLR table function using a table as fake data source] +AS +BEGIN + CREATE TABLE FakeFunctionTests.Expected (a CHAR(1)); + INSERT INTO FakeFunctionTests.Expected VALUES('a'); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt_testutil.AClrTvf', @FakeDataSource = 'FakeFunctionTests.Expected'; + + INSERT INTO FakeFunctionTests.Actual SELECT * FROM tSQLt_testutil.AClrTvf('', ''); + + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake function with one parameter] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int) RETURNS TABLE AS RETURN (SELECT @a AS one);'); + + CREATE TABLE FakeFunctionTests.Expected (a INT); + INSERT INTO FakeFunctionTests.Expected VALUES(1); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'FakeFunctionTests.Expected'; + + INSERT INTO FakeFunctionTests.Actual SELECT * FROM FakeFunctionTests.AFunction(123); + + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake function with multiple parameters] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int, @b int, @c char(1)) RETURNS TABLE AS RETURN (SELECT @a AS one);'); + + CREATE TABLE FakeFunctionTests.Expected (a INT); + INSERT INTO FakeFunctionTests.Expected VALUES(1); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'FakeFunctionTests.Expected'; + + INSERT INTO FakeFunctionTests.Actual SELECT * FROM FakeFunctionTests.AFunction(123, 321, 'a'); + + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake function with VALUES clause as fake data source] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS TABLE AS RETURN (SELECT 777 AS a);'); + + CREATE TABLE #expected (a INT); + INSERT INTO #expected VALUES(1); + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '(VALUES(1)) a(a)'; + + SELECT * INTO #actual FROM FakeFunctionTests.AFunction(); + + EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake function with two part named table as fake data source] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS TABLE AS RETURN (SELECT 777 AS a);'); + + CREATE TABLE #expected (a INT); + INSERT INTO #expected VALUES(1); + + SELECT * INTO FakeFunctionTests.SomeTable FROM #expected; + + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'FakeFunctionTests.SomeTable'; + + SELECT * INTO #actual FROM FakeFunctionTests.AFunction(); + + EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +END; +GO +CREATE TYPE FakeFunctionTests.TableType1001 AS TABLE(SomeInt INT); +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake function with table-type parameter] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@p1 int, @p2 FakeFunctionTests.TableType1001 READONLY, @p3 INT) RETURNS INT AS BEGIN RETURN 0; END;'); + EXEC('CREATE FUNCTION FakeFunctionTests.Fake(@p1 INT,@p2 FakeFunctionTests.TableType1001 READONLY,@p3 INT) RETURNS INT AS BEGIN RETURN (SELECT SUM(SomeInt)+@p1+@p3 FROM @p2); END;'); + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeFunctionName = 'FakeFunctionTests.Fake'; + + DECLARE @Actual INT; + DECLARE @TableParameter FakeFunctionTests.TableType1001; + INSERT INTO @TableParameter(SomeInt)VALUES(10); + INSERT INTO @TableParameter(SomeInt)VALUES(202); + INSERT INTO @TableParameter(SomeInt)VALUES(3303); + + SET @Actual = FakeFunctionTests.AFunction(10000,@TableParameter,220000); + EXEC tSQLt.AssertEqualsString @Expected = 233515, @Actual = @Actual; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake with derived table] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS TABLE AS RETURN (SELECT 777 AS a);'); + + CREATE TABLE #expected (a INT); + INSERT INTO #expected VALUES(1); + + CREATE TABLE #actual (a INT) + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'select 1 as a'; + + INSERT INTO #actual SELECT * FROM FakeFunctionTests.AFunction(); + + EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test can fake with data source table that starts with select] +AS +BEGIN + DECLARE @Expected INT = 1; + DECLARE @Actual INT = 0; + + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS TABLE AS RETURN (SELECT 777 AS a);'); + + CREATE TABLE SelectFakeFunctionTestsTable (a int); + INSERT INTO SelectFakeFunctionTestsTable VALUES (@Expected); + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', + @FakeDataSource = N'SelectFakeFunctionTestsTable'; + + SELECT @Actual = a FROM FakeFunctionTests.AFunction() + EXEC tSQLt.AssertEquals @Expected, @Actual; + +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test Private_PrepareFakeFunctionOutputTable returns table with VALUES] +AS +BEGIN + + DECLARE @NewTable sysname; + + CREATE TABLE #Expected (a int); + INSERT INTO #Expected VALUES(1); + + EXEC tSQLt.Private_PrepareFakeFunctionOutputTable '(VALUES (1)) a(a)', @NewTable OUTPUT; + + EXEC tSQLt.AssertEqualsTable '#Expected', @NewTable; + +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test Private_PrepareFakeFunctionOutputTable returns table with SELECT query] +AS +BEGIN + + DECLARE @NewTable NVARCHAR(MAX); + + EXEC tSQLt.Private_PrepareFakeFunctionOutputTable 'SELECT 1013 AS a', @NewTable OUTPUT; + + EXEC('SELECT TOP(0)A.* INTO FakeFunctionTests.Expected FROM '+@NewTable+' A RIGHT JOIN '+@NewTable+' ON 0=1;'); + INSERT INTO FakeFunctionTests.Expected VALUES(1013); + + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', @NewTable; +END; +GO +CREATE PROC FakeFunctionTests.[test FakeFunction calls tSQLt.Private_MarktSQLtTempObject on new object] +AS +BEGIN + EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS TABLE AS RETURN (SELECT 777 AS a);'); + DECLARE @OriginalObjectId INT = OBJECT_ID('FakeFunctionTests.AFunction'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + TRUNCATE TABLE tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog;--Quirkiness of testing the framework that you use to run the test + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '(VALUES(1)) a(a)'; + + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('FakeFunctionTests.AFunction', N'FUNCTION', OBJECT_NAME(@OriginalObjectId)); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO + +-- THIS CODE CREATES THE SNAPSHOT. +-- When we revisit creating something like FakeFunctionWithSnapshot, we should refer back. +-- DECLARE @newTbleName NVARCHAR(MAX); +-- EXEC tSQLt.Private_PrepareFakeFunctionOutputTable @FakeDataSource, @newTbleName OUTPUT; + +--THESE TESTS FAIL without a snapshot because they use temp tables +--CREATE PROCEDURE FakeFunctionTests.[test can fake function with multiple parameters] +--AS +--BEGIN +-- EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int, @b int, @c char(1)) RETURNS TABLE AS RETURN (SELECT @a AS one);'); + +-- CREATE TABLE #expected (a INT); +-- INSERT INTO #expected VALUES(1); + +-- EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + +-- SELECT * INTO #actual FROM FakeFunctionTests.AFunction(123, 321, 'a'); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test can fake CLR table function using a temp table as fake data source] +--AS +--BEGIN + +-- CREATE TABLE #expected (a CHAR(1)); +-- INSERT INTO #expected VALUES('a'); + +-- EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt_testutil.AClrTvf', @FakeDataSource = '#expected'; + +-- SELECT * INTO #actual FROM tSQLt_testutil.AClrTvf('', ''); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test can fake function with one parameter] +--AS +--BEGIN +-- EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int) RETURNS TABLE AS RETURN (SELECT @a AS one);'); + +-- CREATE TABLE #expected (a INT); +-- INSERT INTO #expected VALUES(1); + +-- EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + +-- SELECT * INTO #actual FROM FakeFunctionTests.AFunction(123); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test can fake Inline table function using a temp table as fake data source] +--AS +--BEGIN +-- EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS TABLE AS RETURN (SELECT 1 AS one);'); + +-- CREATE TABLE #expected (a CHAR(1)); +-- INSERT INTO #expected VALUES('a'); + +-- EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + +-- SELECT * INTO #actual FROM FakeFunctionTests.AFunction(); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test can fake multi-statement table function using a temp table as fake data source] +--AS +--BEGIN +-- EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int) RETURNS @t TABLE (a int) AS BEGIN; +-- INSERT INTO @t (a) VALUES (0) RETURN; END;'); + +-- CREATE TABLE #expected (a CHAR(1)); +-- INSERT INTO #expected VALUES('a'); + +-- EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + +-- SELECT * INTO #actual FROM FakeFunctionTests.AFunction(123); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test Private_PrepareFakeFunctionOutputTable creates snapshot table in passed in schema] +--AS +--BEGIN +-- DECLARE @NewTable NVARCHAR(MAX); + +-- EXEC('CREATE SCHEMA [a random schema];'); + +-- EXEC tSQLt.Private_PrepareFakeFunctionOutputTable @FakeDataSource = 'SELECT 1013 AS a',@SchemaName = 'a random schema', @NewTableName = @NewTable OUT; + +-- SELECT SCHEMA_NAME(O.schema_id) SchemaName INTO #Actual FROM sys.objects O WHERE O.object_id = OBJECT_ID(@NewTable); + +-- SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; +-- INSERT INTO #Expected +-- VALUES('a random schema'); + +-- EXEC tSQLt.AssertEqualsTable #Expected, #Actual +--END; +--GO diff --git a/Tests/FakeTableTests.class.sql b/Tests/FakeTableTests.class.sql index 2b6c1c3d7..49ef0401a 100644 --- a/Tests/FakeTableTests.class.sql +++ b/Tests/FakeTableTests.class.sql @@ -17,49 +17,21 @@ EXEC tSQLt.NewTestClass 'FakeTableTests'; GO -CREATE PROC FakeTableTests.[test that no disabled tests exist] +CREATE PROC FakeTableTests.AssertTableIsNewObjectThatHasNoChildObjects +@TableName NVARCHAR(MAX), +@OriginalObjectId INT AS BEGIN - SELECT name - INTO #Actual - FROM sys.procedures - WHERE ( - LOWER(name) LIKE '_test%' - OR LOWER(name) LIKE 't_est%' - OR LOWER(name) LIKE 'te_st%' - OR LOWER(name) LIKE 'tes_t%' - ) - AND schema_id = SCHEMA_ID(OBJECT_SCHEMA_NAME(@@PROCID)); - - SELECT TOP(0) * INTO #Expected FROM #Actual; - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; -END; -GO - -CREATE PROC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints -@TableName NVARCHAR(MAX) -AS -BEGIN - DECLARE @OldTableObjectId INT; - IF OBJECT_ID(@TableName) IS NULL EXEC tSQLt.Fail 'Table ',@TableName,' does not exist!'; - - SELECT @OldTableObjectId = OBJECT_ID(QUOTENAME(OBJECT_SCHEMA_NAME(major_id))+'.'+QUOTENAME(CAST(value AS NVARCHAR(4000)))) - FROM sys.extended_properties WHERE major_id = OBJECT_ID(@TableName) and name = 'tSQLt.FakeTable_OrgTableName' - - IF @OldTableObjectId IS NULL - EXEC tSQLt.Fail 'Table ',@TableName,' is not a fake table!'; - IF OBJECT_ID(@TableName) = @OldTableObjectId + IF OBJECT_ID(@TableName) = @OriginalObjectId EXEC tSQLt.Fail 'Table ',@TableName,' is not a new object!'; - SELECT QUOTENAME(OBJECT_SCHEMA_NAME(object_id))+'.'+QUOTENAME(OBJECT_NAME(object_id)) ReferencingObjectName - INTO #actual FROM sys.objects WHERE parent_object_id = OBJECT_ID(@TableName); - - SELECT TOP(0) * INTO #expected FROM #actual; + SELECT QUOTENAME(OBJECT_SCHEMA_NAME(object_id))+'.'+QUOTENAME(OBJECT_NAME(object_id)) ReferencingObjectName, type_desc + INTO #ChildObjects FROM sys.objects WHERE parent_object_id = OBJECT_ID(@TableName); - EXEC tSQLt.AssertEqualsTable '#expected','#actual','Unexpected referencing objects found!'; + EXEC tSQLt.AssertEmptyTable @TableName = '#ChildObjects', @Message = 'Unexpected child objects found!'; END GO @@ -67,10 +39,13 @@ CREATE PROC FakeTableTests.[test FakeTable works with 2 part names in first para AS BEGIN CREATE TABLE FakeTableTests.TempTable1(i INT); + DECLARE @OriginalObjectId INT = OBJECT_ID('FakeTableTests.TempTable1'); EXEC tSQLt.FakeTable 'FakeTableTests.TempTable1'; - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; + EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoChildObjects + @TableName = 'FakeTableTests.TempTable1', + @OriginalObjectId = @OriginalObjectId; END; GO @@ -78,10 +53,13 @@ CREATE PROC FakeTableTests.[test FakeTable takes 2 nameless parameters containin AS BEGIN CREATE TABLE FakeTableTests.TempTable1(i INT); + DECLARE @OriginalObjectId INT = OBJECT_ID('FakeTableTests.TempTable1'); EXEC tSQLt.FakeTable 'FakeTableTests','TempTable1'; - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; + EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoChildObjects + @TableName = 'FakeTableTests.TempTable1', + @OriginalObjectId = @OriginalObjectId; END; GO @@ -180,11 +158,9 @@ CREATE PROC FakeTableTests.[test a faked table has no primary key] AS BEGIN CREATE TABLE FakeTableTests.TempTable1(i INT PRIMARY KEY); - + EXEC tSQLt.FakeTable 'FakeTableTests.TempTable1'; - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; - INSERT INTO FakeTableTests.TempTable1 (i) VALUES (1); INSERT INTO FakeTableTests.TempTable1 (i) VALUES (1); END; @@ -197,7 +173,6 @@ BEGIN EXEC tSQLt.FakeTable 'FakeTableTests.TempTable1'; - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; INSERT INTO FakeTableTests.TempTable1 (i) VALUES (5); END; GO @@ -206,11 +181,10 @@ CREATE PROC FakeTableTests.[test a faked table has no foreign keys] AS BEGIN CREATE TABLE FakeTableTests.TempTable0(i INT PRIMARY KEY); - CREATE TABLE FakeTableTests.TempTable1(i INT REFERENCES FakeTableTests.TempTable0(i)); + CREATE TABLE FakeTableTests.TempTable1(i INT REFERENCES FakeTableTests.TempTable0(i)); EXEC tSQLt.FakeTable 'FakeTableTests.TempTable1'; - - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; + INSERT INTO FakeTableTests.TempTable1 (i) VALUES (5); END; GO @@ -222,12 +196,9 @@ BEGIN EXEC tSQLt.FakeTable 'FakeTableTests.TempTable1'; - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; INSERT INTO FakeTableTests.TempTable1 (i) DEFAULT VALUES; - DECLARE @value INT; - SELECT @value = i - FROM FakeTableTests.TempTable1; + DECLARE @value INT = (SELECT i FROM FakeTableTests.TempTable1); EXEC tSQLt.AssertEquals NULL, @value; END; @@ -240,7 +211,6 @@ BEGIN EXEC tSQLt.FakeTable 'FakeTableTests.TempTable1'; - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; INSERT INTO FakeTableTests.TempTable1 (i) VALUES (1); INSERT INTO FakeTableTests.TempTable1 (i) VALUES (1); END; @@ -254,7 +224,6 @@ BEGIN EXEC tSQLt.FakeTable 'FakeTableTests.TempTable1'; - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; INSERT INTO FakeTableTests.TempTable1 (i) VALUES (1); INSERT INTO FakeTableTests.TempTable1 (i) VALUES (1); END; @@ -267,7 +236,6 @@ BEGIN EXEC tSQLt.FakeTable 'FakeTableTests.TempTable1'; - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; INSERT INTO FakeTableTests.TempTable1 (i) VALUES (NULL); END; GO @@ -385,8 +353,31 @@ BEGIN EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO + +CREATE PROC FakeTableTests.[test FakeTable handles column length, precision, and scale] +AS +BEGIN + IF OBJECT_ID('dbo.tst1') IS NOT NULL DROP TABLE dbo.tst1; + + CREATE TABLE dbo.tst1(Length1 VARCHAR(42), Length2 VARCHAR(MAX), Precision_Scale NUMERIC(21,3)); + + SELECT column_id,name,max_length, precision, scale + INTO #Expected + FROM sys.columns + WHERE object_id = OBJECT_ID('dbo.tst1') + + EXEC tSQLt.FakeTable 'dbo.tst1'; + + SELECT column_id,name,max_length, precision, scale + INTO #Actual + FROM sys.columns + WHERE object_id = OBJECT_ID('dbo.tst1') + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO -CREATE PROC FakeTableTests.[test FakeTable works with ugly column and table names] +CREATE PROC FakeTableTests.[test FakeTable works with special characters in column and table names] AS BEGIN IF OBJECT_ID('dbo.[tst!@#$%^&*()_+ 1]') IS NOT NULL DROP TABLE dbo.[tst!@#$%^&*()_+ 1]; @@ -978,7 +969,7 @@ BEGIN CREATE TABLE FakeTableTests.TempTable1(c1 INT NULL, c2 BIGINT NULL, c3 VARCHAR(MAX) NULL); EXEC('CREATE VIEW FakeTableTests.TempView1 AS SELECT * FROM FakeTableTests.TempTable1;'); CREATE SYNONYM FakeTableTests.TempSynonym1 FOR FakeTableTests.TempView1; - + EXEC tSQLt.FakeTable 'FakeTableTests.TempSynonym1'; EXEC tSQLt.AssertEqualsTableSchema @Expected = 'FakeTableTests.TempTable1', @Actual = 'FakeTableTests.TempSynonym1'; @@ -1004,34 +995,51 @@ BEGIN END; GO - CREATE PROC FakeTableTests.[test FakeTable works with two parameters, if they are quoted] AS BEGIN CREATE TABLE FakeTableTests.TempTable1(i INT NOT NULL); + DECLARE @OriginalObjectId INT = OBJECT_ID('FakeTableTests.TempTable1'); + EXEC tSQLt.FakeTable '[FakeTableTests]','[TempTable1]'; - EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; + EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoChildObjects + @TableName = 'FakeTableTests.TempTable1', + @OriginalObjectId = @OriginalObjectId; END; GO - ---CREATE PROC FakeTableTests.[test FakeTable works with cross database synonym] ---AS ---BEGIN --- CREATE TABLE tempdb.dbo.TempTable1(i INT NOT NULL); --- CREATE SYNONYM FakeTableTests.TempTable1 FOR tempdb.dbo.TempTable1 - --- EXEC tSQLt.FakeTable '[FakeTableTests]','[TempTable1]'; +CREATE PROC FakeTableTests.[test FakeTable calls tSQLt.Private_MarktSQLtTempObject on new object] +AS +BEGIN + CREATE TABLE FakeTableTests.TempTable1(i INT NOT NULL); + DECLARE @OriginalObjectId INT = OBJECT_ID('FakeTableTests.TempTable1'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + TRUNCATE TABLE tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog;--Quirkiness of testing the framework that you use to run the test --- EXEC FakeTableTests.AssertTableIsNewObjectThatHasNoConstraints 'FakeTableTests.TempTable1'; - ---END; ---GO - + EXEC tSQLt.FakeTable '[FakeTableTests].[TempTable1]'; + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[FakeTableTests].[TempTable1]', N'TABLE', OBJECT_NAME(@OriginalObjectId)); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROC FakeTableTests.[test FakeTable works if new name of original table requires quoting] +AS +BEGIN + CREATE TABLE FakeTableTests.TempTable1(i INT NULL); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RenameObjectToUniqueName', @CommandToExecute = 'SET @NewName = ''A Name.Needs''''Quoting'';',@CallOriginal = 1; ---ROLLBACK + EXEC tSQLt.FakeTable @TableName = 'FakeTableTests.TempTable1'; + EXEC tSQLt.AssertEqualsTableSchema @Expected = 'FakeTableTests.[A Name.Needs''Quoting]', @Actual = 'FakeTableTests.TempTable1'; +END; +GO diff --git a/Tests/FriendlySQLServerVersionTests.class.sql b/Tests/FriendlySQLServerVersionTests.class.sql new file mode 100644 index 000000000..70338db35 --- /dev/null +++ b/Tests/FriendlySQLServerVersionTests.class.sql @@ -0,0 +1,206 @@ +EXEC tSQLt.NewTestClass 'FriendlySQLServerVersionTests'; +GO +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns NULL if version is unknown] +AS +BEGIN + + SELECT FriendlyVersion INTO #Actual FROM tSQLt.FriendlySQLServerVersion('unknown'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(NULL); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns 2019 if version is 15.00] +AS +BEGIN + + SELECT FriendlyVersion INTO #Actual FROM tSQLt.FriendlySQLServerVersion('15.00'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('2019'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns passed in ProductVersion] +AS +BEGIN + + SELECT ProductVersion INTO #Actual FROM tSQLt.FriendlySQLServerVersion('15.00.1.1234'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('15.00.1.1234'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns 2019 if version is 15.0.2000.5] +AS +BEGIN + + SELECT FriendlyVersion INTO #Actual FROM tSQLt.FriendlySQLServerVersion('15.0.2000.5'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('2019'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns the right friendly versions] +AS +BEGIN + CREATE TABLE #Versions (ProductVersion NVARCHAR(128), FriendlyVersion NVARCHAR(128)); + INSERT INTO #Versions + VALUES('15.0','2019'), + ('14.0','2017'), + ('13.0','2016'), + ('12.0','2014'), + ('11.0','2012'), + ('10.5','2008R2'), + ('10.0','2008'); + + SELECT E.ProductVersion, FSSV.FriendlyVersion + INTO #Actual + FROM #Versions AS E + CROSS APPLY tSQLt.FriendlySQLServerVersion(E.ProductVersion) FSSV; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected SELECT * FROM #Versions; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns the right friendly versions for the official version] +AS +BEGIN + CREATE TABLE #Versions (ProductVersion NVARCHAR(128), FriendlyVersion NVARCHAR(128)); + INSERT INTO #Versions + VALUES('15.0.2080.9','2019'), + ('14.0.2037.2','2017'), + ('13.0.5026.0','2016'), + ('13.0.4001.0','2016'), + ('12.0.6024.0','2014'), + ('12.0.4100.1','2014'), + ('11.0.7001.0','2012'), + ('11.0.3000.00','2012'), + ('10.50.6000.34','2008R2'), + ('10.50.4000.0','2008R2'), + ('10.00.5500.00','2008'), + ('10.0.6000.29','2008'); + + SELECT E.ProductVersion, FSSV.FriendlyVersion + INTO #Actual + FROM #Versions AS E + CROSS APPLY tSQLt.FriendlySQLServerVersion(E.ProductVersion) FSSV; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected SELECT * FROM #Versions; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE FriendlySQLServerVersionTests.[test never returns NULL for the actual ProductVersion] +AS +BEGIN + + SELECT * INTO #Actual + FROM tSQLt.FriendlySQLServerVersion(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128))) + WHERE FriendlyVersion IS NULL; + + EXEC tSQLt.AssertEmptyTable '#Actual'; + +END; +GO +CREATE PROCEDURE FriendlySQLServerVersionTests.AssertReturnsCorrectFriendlyVersion + @Productversion NVARCHAR(128), + @ExpectedVersion NVARCHAR(128) +AS +BEGIN + SELECT FriendlyVersion INTO #Actual + FROM tSQLt.FriendlySQLServerVersion(@Productversion); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(@ExpectedVersion); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](16) +--[@tSQLt:MinSqlMajorVersion](16) +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns 2022 for the actual ProductVersion on 2022] +AS +BEGIN + DECLARE @CurrentProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + EXEC FriendlySQLServerVersionTests.AssertReturnsCorrectFriendlyVersion + @Productversion = @CurrentProductVersion, + @ExpectedVersion = '2022'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](15) +--[@tSQLt:MinSqlMajorVersion](15) +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns 2019 for the actual ProductVersion on 2019] +AS +BEGIN + DECLARE @CurrentProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + EXEC FriendlySQLServerVersionTests.AssertReturnsCorrectFriendlyVersion + @Productversion = @CurrentProductVersion, + @ExpectedVersion = '2019'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](14) +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns 2017 for the actual ProductVersion on 2017] +AS +BEGIN + DECLARE @CurrentProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + EXEC FriendlySQLServerVersionTests.AssertReturnsCorrectFriendlyVersion + @Productversion = @CurrentProductVersion, + @ExpectedVersion = '2017'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](13) +--[@tSQLt:MinSqlMajorVersion](13) +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns 2016 for the actual ProductVersion on 2016] +AS +BEGIN + DECLARE @CurrentProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + EXEC FriendlySQLServerVersionTests.AssertReturnsCorrectFriendlyVersion + @Productversion = @CurrentProductVersion, + @ExpectedVersion = '2016'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](12) +--[@tSQLt:MinSqlMajorVersion](12) +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns 2014 for the actual ProductVersion on 2014] +AS +BEGIN + DECLARE @CurrentProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + EXEC FriendlySQLServerVersionTests.AssertReturnsCorrectFriendlyVersion + @Productversion = @CurrentProductVersion, + @ExpectedVersion = '2014'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](11) +--[@tSQLt:MinSqlMajorVersion](11) +CREATE PROCEDURE FriendlySQLServerVersionTests.[test returns 2012 for the actual ProductVersion on 2012] +AS +BEGIN + DECLARE @CurrentProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + EXEC FriendlySQLServerVersionTests.AssertReturnsCorrectFriendlyVersion + @Productversion = @CurrentProductVersion, + @ExpectedVersion = '2012'; +END; +GO diff --git a/Tests/InfoTests.class.sql b/Tests/InfoTests.class.sql index e9b813d1e..e6d6b4821 100644 --- a/Tests/InfoTests.class.sql +++ b/Tests/InfoTests.class.sql @@ -23,9 +23,8 @@ BEGIN DECLARE @SigningKeyPattern NVARCHAR(MAX); DECLARE @ClrInfo NVARCHAR(MAX); - SELECT @SigningKeyPattern = '%publickeytoken='+PBH.bare+',%' - FROM tSQLt.Info() I - CROSS APPLY tSQLt.Private_Bin2Hex(I.ClrSigningKey) AS PBH; + SELECT @SigningKeyPattern = '%publickeytoken='+LOWER(CONVERT(NVARCHAR(MAX),I.ClrSigningKey,2))+',%' + FROM tSQLt.Info() I; SELECT @ClrInfo=clr_name FROM sys.assemblies WHERE name='tSQLtCLR' @@ -35,7 +34,28 @@ GO CREATE FUNCTION InfoTests.[42.17.1986.57]() RETURNS TABLE AS -RETURN SELECT CAST(N'42.17.1986.57' AS NVARCHAR(128)) AS ProductVersion, 'My Edition' AS Edition; +RETURN SELECT CAST(N'42.17.1986.57' AS NVARCHAR(128)) AS ProductVersion, 'My Edition' AS Edition, NULL HostPlatform; +GO +CREATE PROCEDURE InfoTests.[test returns HostPlatform] +AS +BEGIN + + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_HostPlatform'; + EXEC('INSERT INTO tSQLt.Private_HostPlatform(host_platform) VALUES (''SomePlatform'');'); + + SELECT I.HostPlatform + INTO #Actual + FROM tSQLt.Info() AS I; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + VALUES('SomePlatform'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; GO CREATE PROCEDURE InfoTests.[test returns SqlVersion and SqlBuild] AS @@ -60,7 +80,6 @@ GO CREATE PROCEDURE InfoTests.[test returns SqlEdition] AS BEGIN - EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'InfoTests.[42.17.1986.57]'; SELECT I.SqlEdition @@ -76,4 +95,67 @@ BEGIN EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; +GO +CREATE FUNCTION InfoTests.[return 97.53]() +RETURNS TABLE +AS +RETURN SELECT CAST(97.53 AS NUMERIC(10,2)) AS SqlVersion; +GO +CREATE PROCEDURE InfoTests.[test returns InstalledOnSqlVersion] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_InstallationInfo', @FakeFunctionName = 'InfoTests.[return 97.53]'; + + SELECT I.InstalledOnSqlVersion + INTO #Actual + FROM tSQLt.Info() AS I; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + VALUES(97.53); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE InfoTests.[test returns correct HostPlatform on SQL versions >= 2017] +AS +BEGIN + + SELECT PSV.HostPlatform + INTO #Actual + FROM tSQLt.Info() AS PSV; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + SELECT + host_platform HostPlatform + FROM sys.dm_os_host_info; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +--[@tSQLt:MaxSqlMajorVersion](13) +CREATE PROCEDURE InfoTests.[test returns 'Windows' for HostPlatform on SQL versions < 2017] +AS +BEGIN + + SELECT PSV.HostPlatform + INTO #Actual + FROM tSQLt.Info() AS PSV; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected VALUES('Windows'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; diff --git a/Tests/NewTestClassTests.class.sql b/Tests/NewTestClassTests.class.sql index ce7e1085f..029a62201 100644 --- a/Tests/NewTestClassTests.class.sql +++ b/Tests/NewTestClassTests.class.sql @@ -30,6 +30,9 @@ CREATE PROCEDURE NewTestClassTests.[test NewTestClass should throw an error if t AS BEGIN DECLARE @Err NVARCHAR(MAX); SET @Err = 'NO ERROR'; + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + EXEC('CREATE SCHEMA MySchema;'); BEGIN TRY @@ -39,7 +42,11 @@ BEGIN SET @Err = ERROR_MESSAGE(); END CATCH - IF @Err NOT LIKE '%Attempted to execute tSQLt.NewTestClass on ''MySchema'' which is an existing schema but not a test class%(Error originated in Private_DisallowOverwritingNonTestSchema)%' + DECLARE @ExpectedErr NVARCHAR(MAX) = + '%Attempted to execute tSQLt.NewTestClass on ''MySchema'' which is an existing schema but not a test class%(Error originated in '+ + CASE WHEN @ProductMajorVersion >= 14 THEN 'tSQLt.' ELSE '' END+ + 'Private_DisallowOverwritingNonTestSchema)%' + IF @Err NOT LIKE @ExpectedErr BEGIN EXEC tSQLt.Fail 'Unexpected error message was: ', @Err; END; @@ -50,6 +57,9 @@ CREATE PROCEDURE NewTestClassTests.[test the NewTestClass-"not a test class" err AS BEGIN DECLARE @ErrProc NVARCHAR(MAX); SET @ErrProc = 'NO ERROR'; + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + EXEC('CREATE SCHEMA MySchema;'); BEGIN TRY @@ -59,7 +69,8 @@ BEGIN SET @ErrProc = ERROR_PROCEDURE(); END CATCH - EXEC tSQLt.AssertEqualsString 'NewTestClass', @ErrProc; + DECLARE @ExpectedErrProc NVARCHAR(MAX) = CASE WHEN @ProductMajorVersion >= 14 THEN 'tSQLt.' ELSE '' END + 'NewTestClass' + EXEC tSQLt.AssertEqualsString @ExpectedErrProc, @ErrProc; END; GO diff --git a/Tests/Private_AssertNoSideEffectsTests.class.sql b/Tests/Private_AssertNoSideEffectsTests.class.sql new file mode 100644 index 000000000..549340b07 --- /dev/null +++ b/Tests/Private_AssertNoSideEffectsTests.class.sql @@ -0,0 +1,55 @@ +EXEC tSQLt.NewTestClass 'Private_AssertNoSideEffectsTests'; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION Private_AssertNoSideEffectsTests.[Faked_GenerateCommand]( + @BeforeExecutionObjectSnapshotTableName NVARCHAR(MAX), + @AfterExecutionObjectSnapshotTableName NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT @BeforeExecutionObjectSnapshotTableName + '<><><>' + @AfterExecutionObjectSnapshotTableName Command; +GO +CREATE PROCEDURE Private_AssertNoSideEffectsTests.[test Private_AssertNoSideEffects is using Private_CleanUpCmdHandler] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_AssertNoSideEffects_GenerateCommand', @FakeFunctionName = 'Private_AssertNoSideEffectsTests.[Faked_GenerateCommand]'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler'; + + EXEC tSQLt.Private_AssertNoSideEffects @BeforeExecutionObjectSnapshotTableName = 'BeforeTable', @AfterExecutionObjectSnapshotTableName = 'AfterTable', @TestResult = 'Result1', @TestMsg = 'Message1'; + + SELECT CleanUpCmd, TestResult, TestMsg INTO #Actual FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES ('BeforeTable<><><>AfterTable','Result1','Message1'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_AssertNoSideEffectsTests.[test Private_AssertNoSideEffects is using Private_CleanUpCmdHandler with @TestResult and @TestMsg as OUTPUT parameters] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler', @CommandToExecute = 'SET @TestResult = ''Altered Test Result'';SET @TestMsg = ''Altered Test Message'';'; + + DECLARE @TestResult NVARCHAR(MAX) = 'Result1'; + DECLARE @TestMessage NVARCHAR(MAX) = 'Message1'; + + EXEC tSQLt.Private_AssertNoSideEffects @BeforeExecutionObjectSnapshotTableName = 'BeforeTable', @AfterExecutionObjectSnapshotTableName = 'AfterTable', @TestResult = @TestResult OUT, @TestMsg = @TestMessage OUT; + + SELECT @TestResult TestResult, @TestMessage TestMsg INTO #Actual + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES ('Altered Test Result','Altered Test Message'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/Private_CleanUpCmdHandlerTests.class.sql b/Tests/Private_CleanUpCmdHandlerTests.class.sql new file mode 100644 index 000000000..a2caa9613 --- /dev/null +++ b/Tests/Private_CleanUpCmdHandlerTests.class.sql @@ -0,0 +1,44 @@ +EXEC tSQLt.NewTestClass 'Private_CleanUpCmdHandlerTests'; +GO +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION Private_CleanUpCmdHandlerTests.[return 42134213 if correct error]() +RETURNS TABLE +AS +RETURN + SELECT '9999' + ERROR_MESSAGE() FormattedError; +GO +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION Private_CleanUpCmdHandlerTests.[return 42424242+@NewMessage, @NewResult]( + @PrevMessage NVARCHAR(MAX), + @PrevResult NVARCHAR(MAX), + @NewMessage NVARCHAR(MAX), + @NewResult NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT @PrevResult+':7777:'+@NewMessage Message, @NewResult Result +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpCmdHandlerTests.[test is using the two error functions correctly] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetFormattedErrorInfo', @FakeFunctionName = 'Private_CleanUpCmdHandlerTests.[return 42134213 if correct error]'; + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_HandleMessageAndResult', @FakeFunctionName = 'Private_CleanUpCmdHandlerTests.[return 42424242+@NewMessage, @NewResult]'; + + DECLARE @TestResult NVARCHAR(MAX) = 'PrevResult'; + DECLARE @TestMessage NVARCHAR(MAX) = 'PrevMessage'; + EXEC tSQLt.Private_CleanUpCmdHandler @CleanUpCmd = 'RAISERROR(''ACleanUpError'',16,10);', @TestResult=@TestResult OUT, @TestMsg = @TestMessage OUT, @ResultInCaseOfError = 'NewResult'; + + SELECT @TestMessage Message, @TestResult Result INTO #Actual; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('PrevResult:7777:Error during clean up: (9999ACleanUpError)','NewResult'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO diff --git a/Tests/Private_CleanUpTests.class.sql b/Tests/Private_CleanUpTests.class.sql new file mode 100644 index 000000000..2a07b968e --- /dev/null +++ b/Tests/Private_CleanUpTests.class.sql @@ -0,0 +1,123 @@ +EXEC tSQLt.NewTestClass 'Private_CleanUpTests'; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test calls tSQLt.UndoTestDoubles with @Force=0] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = NULL; + + SELECT _id_, Force INTO #Actual FROM tSQLt.UndoTestDoubles_SpyProcedureLog; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,0); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test calls tSQLt.Private_NoTransactionHandleTables with @Action='Reset'] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = NULL; + + SELECT _id_, Action INTO #Actual FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'Reset'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test calls tSQLt.Private_CleanUpCmdHandler for only UndoTestDoubles and HandleTables in the correct order] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler'; + + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = NULL; + + SELECT _id_, CleanUpCmd INTO #Actual FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog; + SELECT TOP(0) A._id_, A.CleanUpCmd AS [%CleanUpCmd] INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'%tSQLt.Private_NoTransactionHandleTables%'),(2,'%tSQLt.UndoTestDoubles%'); + + SELECT * INTO #Compare + FROM( + SELECT '>' _R_,* FROM #Actual AS A WHERE NOT EXISTS(SELECT 1 FROM #Expected E WHERE A._id_ = E._id_ AND A.CleanUpCmd LIKE E.[%CleanUpCmd]) + UNION ALL + SELECT '<' _R_,* FROM #Expected AS E WHERE NOT EXISTS(SELECT 1 FROM #Actual A WHERE A._id_ = E._id_ AND A.CleanUpCmd LIKE E.[%CleanUpCmd]) + )X + EXEC tSQLt.AssertEmptyTable @TableName = '#Compare'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test UndoTestDoubles error message is appended to @ErrorMsg] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles', @CommandToExecute = 'RAISERROR(''some cleanup error'',16, 10)'; + + DECLARE @ErrorMsg NVARCHAR(MAX) = 'previous error'; + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = @ErrorMsg OUT; + + EXEC tSQLt.AssertLike @ExpectedPattern = 'previous error%some cleanup error%', @Actual = @ErrorMsg; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test UndoTestDoubles error causes @Result to be set to Abort] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles', @CommandToExecute = 'RAISERROR(''some cleanup error'',16, 10)'; + + DECLARE @Result NVARCHAR(MAX) = 'NOT ERROR'; + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = @Result OUT, @ErrorMsg = NULL; + + EXEC tSQLt.AssertEqualsString @Expected = 'Abort', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test HandleTables error is appended to @ErrorMsg] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'RAISERROR(''some cleanup error'',16, 10)'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + + DECLARE @ErrorMsg NVARCHAR(MAX) = 'previous error'; + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = @ErrorMsg OUT; + + EXEC tSQLt.AssertLike @ExpectedPattern = 'previous error%some cleanup error%', @Actual = @ErrorMsg; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test HandleTables error causes @Result to be set to FATAL] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'RAISERROR(''some cleanup error'',16, 10)'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + + DECLARE @Result NVARCHAR(MAX) = 'NOT ERROR'; + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = @Result OUT, @ErrorMsg = NULL; + + EXEC tSQLt.AssertEqualsString @Expected = 'FATAL', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/Private_CreateFakeFunctionStatementTests.class.sql b/Tests/Private_CreateFakeFunctionStatementTests.class.sql new file mode 100644 index 000000000..997864d72 --- /dev/null +++ b/Tests/Private_CreateFakeFunctionStatementTests.class.sql @@ -0,0 +1,224 @@ +EXEC tSQLt.NewTestClass 'Private_CreateFakeFunctionStatementTests'; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test works for simple SVF] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleSVF() RETURNS INT AS BEGIN RETURN NULL; END;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleSVF'); + + DECLARE @Actual NVARCHAR(MAX) = (SELECT CreateStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,NULL)); + + DECLARE @Expected NVARCHAR(MAX) = 'CREATE FUNCTION [Private_CreateFakeFunctionStatementTests].[ASimpleSVF]() RETURNS [sys].[int] AS BEGIN RETURN CAST(NULL AS [sys].[int]); END;'; + + --SELECT * FROM sys.parameters AS P WHERE P.object_id = @FunctionObjectId; + + EXEC tSQLt.AssertEqualsString @Expected = @Expected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test works for another simple SVF] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.AnotherSimpleSVF() RETURNS VARCHAR(MAX) AS BEGIN RETURN NULL; END;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.AnotherSimpleSVF'); + + DECLARE @Actual NVARCHAR(MAX) = (SELECT CreateStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,NULL)); + + DECLARE @Expected NVARCHAR(MAX) = 'CREATE FUNCTION [Private_CreateFakeFunctionStatementTests].[AnotherSimpleSVF]() RETURNS [sys].[varchar](MAX) AS BEGIN RETURN CAST(NULL AS [sys].[varchar](MAX)); END;'; + + EXEC tSQLt.AssertEqualsString @Expected = @Expected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test works for SVF with one parameter] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleSVF(@P1 INT) RETURNS INT AS BEGIN RETURN NULL; END;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleSVF'); + + DECLARE @Actual NVARCHAR(MAX) = (SELECT CreateStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,NULL)); + + DECLARE @Expected NVARCHAR(MAX) = 'CREATE FUNCTION [Private_CreateFakeFunctionStatementTests].[ASimpleSVF](@P1 [sys].[int]) RETURNS [sys].[int] AS BEGIN RETURN CAST(NULL AS [sys].[int]); END;'; + + EXEC tSQLt.AssertEqualsString @Expected = @Expected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test works for SVF with several parameters] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleSVF(@P1 INT,@P2 NVARCHAR(MAX),@P3 DATETIME2) RETURNS INT AS BEGIN RETURN NULL; END;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleSVF'); + + DECLARE @Actual NVARCHAR(MAX) = (SELECT CreateStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,NULL)); + + DECLARE @Expected NVARCHAR(MAX) = 'CREATE FUNCTION [Private_CreateFakeFunctionStatementTests].[ASimpleSVF](@P1 [sys].[int],@P2 [sys].[nvarchar](MAX),@P3 [sys].[datetime2](7)) RETURNS [sys].[int] AS BEGIN RETURN CAST(NULL AS [sys].[int]); END;'; + + EXEC tSQLt.AssertEqualsString @Expected = @Expected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test produces executable statement for SVF] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleSVF(@P1 INT,@P2 NVARCHAR(MAX),@P3 DATETIME2) RETURNS INT AS BEGIN RETURN NULL; END;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleSVF'); + SELECT + P.name, + P.parameter_id, + P.system_type_id, + P.user_type_id, + P.max_length, + P.precision, + P.scale, + P.is_output + INTO #Expected + FROM sys.parameters AS P WHERE P.object_id = @FunctionObjectId; + + DECLARE @Statement NVARCHAR(MAX) = (SELECT CreateStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,NULL)); + + DROP FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleSVF; + EXEC(@Statement); + + SELECT + P.name, + P.parameter_id, + P.system_type_id, + P.user_type_id, + P.max_length, + P.precision, + P.scale, + P.is_output + INTO #Actual + FROM sys.parameters AS P WHERE P.object_id = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleSVF'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test works for simple TVF] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleTVF() RETURNS TABLE AS RETURN SELECT CAST(1 AS INT) C1;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleTVF'); + + DECLARE @Actual NVARCHAR(MAX) = (SELECT CreateStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,NULL)); + + DECLARE @Expected NVARCHAR(MAX) = 'CREATE FUNCTION [Private_CreateFakeFunctionStatementTests].[ASimpleTVF]() RETURNS TABLE AS RETURN SELECT TOP(0) CAST(NULL AS [sys].[int]) AS [C1];'; + + EXEC tSQLt.AssertEqualsString @Expected = @Expected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test works for TVF with parameters and multiple return columns] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleTVF(@P1 BIGINT,@P2 DATE,@P3 CHAR(17),@P4 BIT) RETURNS TABLE AS RETURN SELECT CAST(1 AS SMALLINT) C1, CAST(NULL AS DATETIME) C2, CAST(NULL AS XML) C3;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleTVF'); + + DECLARE @Actual NVARCHAR(MAX) = (SELECT CreateStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,NULL)); + + DECLARE @Expected NVARCHAR(MAX) = 'CREATE FUNCTION [Private_CreateFakeFunctionStatementTests].[ASimpleTVF](@P1 [sys].[bigint],@P2 [sys].[date],@P3 [sys].[char](17),@P4 [sys].[bit]) RETURNS TABLE AS RETURN SELECT TOP(0) CAST(NULL AS [sys].[smallint]) AS [C1],CAST(NULL AS [sys].[datetime]) AS [C2],CAST(NULL AS [sys].[xml]) AS [C3];'; + + EXEC tSQLt.AssertEqualsString @Expected = @Expected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test returns executable statement for TVF] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleTVF(@P1 BIGINT,@P2 DATE,@P3 CHAR(17),@P4 BIT) RETURNS TABLE AS RETURN SELECT CAST(1 AS SMALLINT) C1, CAST(NULL AS DATETIME) C2, CAST(NULL AS XML) C3;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleTVF'); + + SELECT + source = 'P', + P.name, + P.parameter_id, + P.system_type_id, + P.user_type_id, + P.max_length, + P.precision, + P.scale, + P.is_output + INTO #Expected + FROM sys.parameters AS P WHERE P.object_id = @FunctionObjectId + UNION ALL + SELECT + source = 'C', + C.name, + C.column_id, + C.system_type_id, + C.user_type_id, + C.max_length, + C.precision, + C.scale, + NULL + FROM sys.columns AS C WHERE C.object_id = @FunctionObjectId; + + DECLARE @Statement NVARCHAR(MAX) = (SELECT CreateStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,NULL)); + + DROP FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleTVF; + EXEC(@Statement); + + SELECT + source = 'P', + P.name, + P.parameter_id, + P.system_type_id, + P.user_type_id, + P.max_length, + P.precision, + P.scale, + P.is_output + INTO #Actual + FROM sys.parameters AS P WHERE P.object_id = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleTVF') + UNION ALL + SELECT + source = 'C', + C.name, + C.column_id, + C.system_type_id, + C.user_type_id, + C.max_length, + C.precision, + C.scale, + NULL + FROM sys.columns AS C WHERE C.object_id = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleTVF'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test allows for return value for SVF to be specified] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.AnotherSimpleSVF() RETURNS NUMERIC(10,2) AS BEGIN RETURN NULL; END;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.AnotherSimpleSVF'); + + DECLARE @Actual NVARCHAR(MAX) = (SELECT CreateStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,'42.84')); + + DECLARE @Expected NVARCHAR(MAX) = 'CREATE FUNCTION [Private_CreateFakeFunctionStatementTests].[AnotherSimpleSVF]() RETURNS [sys].[numeric](10,2) AS BEGIN RETURN CAST(''42.84'' AS [sys].[numeric](10,2)); END;'; + + EXEC tSQLt.AssertEqualsString @Expected = @Expected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test returns type only select statement] +AS +BEGIN + EXEC('CREATE FUNCTION Private_CreateFakeFunctionStatementTests.ASimpleTVF(@P1 BIGINT,@P2 DATE,@P3 CHAR(17),@P4 BIT) RETURNS TABLE AS RETURN SELECT CAST(1 AS SMALLINT) C1, CAST(NULL AS DATETIME) C2, CAST(NULL AS XML) C3;'); + DECLARE @FunctionObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleTVF'); + + DECLARE @Actual NVARCHAR(MAX) = (SELECT TypeOnlySelectStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@FunctionObjectId,NULL)); + + DECLARE @Expected NVARCHAR(MAX) = 'SELECT TOP(0) CAST(NULL AS [sys].[smallint]) AS [C1],CAST(NULL AS [sys].[datetime]) AS [C2],CAST(NULL AS [sys].[xml]) AS [C3]'; + + EXEC tSQLt.AssertEqualsString @Expected = @Expected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE Private_CreateFakeFunctionStatementTests.[test also returns type only select statement for views] +AS +BEGIN + EXEC('CREATE VIEW Private_CreateFakeFunctionStatementTests.ASimpleView AS SELECT CAST(1 AS BIGINT) C1, CAST(NULL AS NVARCHAR(17)) C2, CAST(NULL AS FLOAT) C3;'); + DECLARE @ViewObjectId INT = OBJECT_ID('Private_CreateFakeFunctionStatementTests.ASimpleView'); + + DECLARE @Actual NVARCHAR(MAX) = (SELECT TypeOnlySelectStatement FROM tSQLt.Private_CreateFakeFunctionStatement(@ViewObjectId,NULL)); + + DECLARE @Expected NVARCHAR(MAX) = 'SELECT TOP(0) CAST(NULL AS [sys].[bigint]) AS [C1],CAST(NULL AS [sys].[nvarchar](17)) AS [C2],CAST(NULL AS [sys].[float]) AS [C3]'; + + EXEC tSQLt.AssertEqualsString @Expected = @Expected, @Actual = @Actual; + +END; +GO + diff --git a/Tests/Private_GetAnnotationListTests.class.sql b/Tests/Private_GetAnnotationListTests.class.sql new file mode 100644 index 000000000..f4c15c442 --- /dev/null +++ b/Tests/Private_GetAnnotationListTests.class.sql @@ -0,0 +1,212 @@ +EXEC tSQLt.NewTestClass 'Private_GetAnnotationListTests'; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test returns empty result if there's no annotation] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +'CREATE...' + SELECT Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test finds a simple annotation and returns it without --] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +'-'+'-[@tSQLt:MyAnnotation]() +CREATE...' + SELECT Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[@tSQLt:MyAnnotation]()'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test annotation is ignored if there are additional non-space characters in front of --] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +'X-'+'-[@tSQLt:MyAnnotation]() +CREATE...' + SELECT Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test annotation is valid if there are spaces in front of --] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +' -'+'-[@tSQLt:MyAnnotation]() +CREATE...' + SELECT Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('[@tSQLt:MyAnnotation]()'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test annotation is valid if there are tabs in front of --] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +NCHAR(9)+'-'+'-[@tSQLt:MyAnnotation]() +CREATE...' + SELECT Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('[@tSQLt:MyAnnotation]()'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test removes trailing spaces] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +'-'+'-[@tSQLt:MyAnnotation]() '+' +CREATE...' + SELECT '>>>'+Annotation+'<<<' AS BracketedAnnotation, LEN(Annotation) AnnotationLength + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('>>>[@tSQLt:MyAnnotation]()<<<',23); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test removes trailing tabs] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +'-'+'-[@tSQLt:MyAnnotation]()'+NCHAR(9)+' +CREATE...' + SELECT '>>>'+Annotation+'<<<' AS BracketedAnnotation, LEN(Annotation) AnnotationLength + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('>>>[@tSQLt:MyAnnotation]()<<<',23); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test returns all annotations] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +'-'+'-[@tSQLt:MyAnnotation1]() +-'+'-[@tSQLt:MyAnnotation2]() +-'+'-[@tSQLt:MyAnnotation3]() +CREATE...' + SELECT Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES + ('[@tSQLt:MyAnnotation1]()'), + ('[@tSQLt:MyAnnotation2]()'), + ('[@tSQLt:MyAnnotation3]()'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test returns annotations with order number based on where they appear] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +'-'+'-[@tSQLt:MyAnnotation1]() +-'+'-[@tSQLt:MyAnnotation3]() +-'+'-[@tSQLt:MyAnnotation2]() +CREATE...' + SELECT AnnotationNo,Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES + (1,'[@tSQLt:MyAnnotation1]()'), + (2,'[@tSQLt:MyAnnotation3]()'), + (3,'[@tSQLt:MyAnnotation2]()'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test returns annotations anywhere in the code (as long as they are on their own line)] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +'CREATE PROCEDURE dbo.something +-'+'-[@tSQLt:MyAnnotation1]() +AS +BEGIN +SELECT Something +/* +-'+'-[@tSQLt:MyAnnotation2]() +*/ +FROM dbo.sometable +-'+'-[@tSQLt:MyAnnotation3]() +WHERE 0=1; +-'+'-[@tSQLt:MyAnnotation4]() +RETURN 0; +-'+'-[@tSQLt:MyAnnotation5]() +END; +-'+'-[@tSQLt:MyAnnotation6]()'; + + SELECT Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES + ('[@tSQLt:MyAnnotation1]()'), + ('[@tSQLt:MyAnnotation2]()'), + ('[@tSQLt:MyAnnotation3]()'), + ('[@tSQLt:MyAnnotation4]()'), + ('[@tSQLt:MyAnnotation5]()'), + ('[@tSQLt:MyAnnotation6]()'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test return full line with all parameters] +AS +BEGIN + DECLARE @proc NVARCHAR(MAX) = +'-'+'-[@tSQLt:MyAnnotation]() ''some string'',123,''string with ()'',''string with []'',''@'',''-'+'-[@tSQLt:MyAnnotation]()'' --this is still invalid +CREATE...' + SELECT Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(@proc); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[@tSQLt:MyAnnotation]() ''some string'',123,''string with ()'',''string with []'',''@'',''-'+'-[@tSQLt:MyAnnotation]()'' --this is still invalid'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_GetAnnotationListTests.[test returns empty result if @ProcedureDefinition IS NULL] +AS +BEGIN + SELECT Annotation + INTO #Actual + FROM tSQLt.Private_GetAnnotationList(NULL); + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO + + diff --git a/Tests/Private_GetFormattedErrorInfoTests.class.sql b/Tests/Private_GetFormattedErrorInfoTests.class.sql new file mode 100644 index 000000000..c57fc1929 --- /dev/null +++ b/Tests/Private_GetFormattedErrorInfoTests.class.sql @@ -0,0 +1,90 @@ +EXEC tSQLt.NewTestClass 'Private_GetFormattedErrorInfoTests'; +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test does not return null] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX) = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + + EXEC tSQLt.AssertEqualsString @Expected = 'Message: | Procedure: | Severity, State: , | Number: ', @Actual = @FormattedError; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test returns the ERROR information formatted correctly] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX); + + BEGIN TRY + EXEC ('RAISERROR(''my test message'', 15, 11);'); + END TRY + BEGIN CATCH + SET @FormattedError = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + END CATCH; + + EXEC tSQLt.AssertEqualsString @Expected = 'Message: my test message | Procedure: (1) | Severity, State: 15, 11 | Number: 50000', @Actual = @FormattedError; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test returns the correct ERROR number] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX); + + BEGIN TRY + EXEC ('RAISERROR (13042,14,13);'); + END TRY + BEGIN CATCH + SET @FormattedError = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + END CATCH; + + EXEC tSQLt.AssertLike @ExpectedPattern = '%| Number: 13042', @Actual = @FormattedError; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test returns the correct ERROR procedure name and line number] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX); + + BEGIN TRY + EXEC ('/*Line 1*/CREATE PROCEDURE Private_GetFormattedErrorInfoTests.myInnerError + /*Line 2*/AS + /*Line 3*/BEGIN + /*Line 4*/ RAISERROR (13042,14,13); + /*Line 5*/END;'); + EXEC ('Private_GetFormattedErrorInfoTests.myInnerError'); + END TRY + BEGIN CATCH + SET @FormattedError = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + END CATCH; + + EXEC tSQLt.AssertLike @ExpectedPattern = '%| Procedure: %myInnerError (4) |%', @Actual = @FormattedError; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test returns the correct ERROR procedure name and line number for #tempProcedure] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX); + + BEGIN TRY + EXEC ('/*Line 1*/CREATE PROCEDURE #myInnerError + /*Line 2*/AS + /*Line 3*/BEGIN + /*Line 4*/ RAISERROR (13042,14,13); + /*Line 5*/END;'); + EXEC #myInnerError; + END TRY + BEGIN CATCH + SET @FormattedError = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + END CATCH; + + EXEC tSQLt.AssertLike @ExpectedPattern = '%| Procedure: #myInnerError% (4) |%', @Actual = @FormattedError; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/Private_GetFullTypeNameTests.class.sql b/Tests/Private_GetFullTypeNameTests.class.sql index 946e80745..c9b9af89e 100644 --- a/Tests/Private_GetFullTypeNameTests.class.sql +++ b/Tests/Private_GetFullTypeNameTests.class.sql @@ -90,6 +90,42 @@ BEGIN END GO +CREATE PROC Private_GetFullTypeNameTests.[test Private_GetFullTypeName should properly return DATETIME2 parameters] +AS +BEGIN + DECLARE @Result VARCHAR(MAX); + + SELECT @Result = TypeName + FROM tSQLt.Private_GetFullTypeName(TYPE_ID('datetime2'), NULL, NULL, 3, NULL); + + EXEC tSQLt.AssertEqualsString '[sys].[datetime2](3)', @Result; +END +GO + +CREATE PROC Private_GetFullTypeNameTests.[test Private_GetFullTypeName should properly return TIME parameters] +AS +BEGIN + DECLARE @Result VARCHAR(MAX); + + SELECT @Result = TypeName + FROM tSQLt.Private_GetFullTypeName(TYPE_ID('time'), NULL, NULL, 4, NULL); + + EXEC tSQLt.AssertEqualsString '[sys].[time](4)', @Result; +END +GO + +CREATE PROC Private_GetFullTypeNameTests.[test Private_GetFullTypeName should properly return DATETIMEOFFSET parameters] +AS +BEGIN + DECLARE @Result VARCHAR(MAX); + + SELECT @Result = TypeName + FROM tSQLt.Private_GetFullTypeName(TYPE_ID('datetimeoffset'), NULL, NULL, 6, NULL); + + EXEC tSQLt.AssertEqualsString '[sys].[datetimeoffset](6)', @Result; +END +GO + CREATE PROC Private_GetFullTypeNameTests.[test Private_GetFullTypeName should properly return typeName when all parameters are valued] AS BEGIN @@ -241,3 +277,40 @@ BEGIN EXEC tSQLt.AssertEqualsTable 'dbo.Parms','#Actual'; END; GO +CREATE PROC Private_GetFullTypeNameTests.[test Private_GetFullTypeName should handle all 2008 compatible data types] +AS +BEGIN + + CREATE TABLE dbo.Parms( + ColumnName NVARCHAR(MAX), + ColumnType NVARCHAR(MAX) + ); + + INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.date]', '[sys].[date]'); + INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.datetime2]', '[sys].[datetime2](7)'); + INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.datetimeoffset]', '[sys].[datetimeoffset](7)'); + INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.geography]', '[sys].[geography]'); + INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.geometry]', '[sys].[geometry]'); + INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.hierarchyid]', '[sys].[hierarchyid]'); + INSERT INTO dbo.Parms(ColumnName, ColumnType) VALUES ('[sys.time]', '[sys].[time](7)'); + + DECLARE @Cmd NVARCHAR(MAX); + SET @Cmd = STUFF(( + SELECT ', ' + ColumnName + ' ' + ColumnType + FROM dbo.Parms + FOR XML PATH(''), TYPE + ).value('.','NVARCHAR(MAX)'),1,2,''); + SET @Cmd = 'CREATE TABLE dbo.tst1(' + @Cmd + ');'; + + IF OBJECT_ID('dbo.tst1') IS NOT NULL DROP TABLE dbo.tst1; + EXEC(@Cmd); + + SELECT QUOTENAME(c.name) ColumnName, t.TypeName AS ColumnType + INTO #Actual + FROM sys.columns c + CROSS APPLY tSQLt.Private_GetFullTypeName(c.user_type_id,c.max_length, c.precision, c.scale, c.collation_name) t + WHERE c.object_id = OBJECT_ID('dbo.tst1'); + + EXEC tSQLt.AssertEqualsTable 'dbo.Parms','#Actual'; +END; +GO diff --git a/Tests/Private_GetSQLProductMajorVersionTests.class.sql b/Tests/Private_GetSQLProductMajorVersionTests.class.sql new file mode 100644 index 000000000..141b2bc6e --- /dev/null +++ b/Tests/Private_GetSQLProductMajorVersionTests.class.sql @@ -0,0 +1,23 @@ +EXEC tSQLt.NewTestClass 'Private_GetSQLProductMajorVersionTests'; +GO +--[@tSQLt:MinSqlMajorVersion](10) +--[@tSQLt:MaxSqlMajorVersion](10) +CREATE PROCEDURE Private_GetSQLProductMajorVersionTests.[test return correct version on SQL Version = 10] +AS +BEGIN + DECLARE @Version INT; + EXEC @Version = tSQLt.Private_GetSQLProductMajorVersion; + + EXEC tSQLt.AssertEquals @Expected = 10, @Actual = @Version; +END; +GO +--[@tSQLt:MinSqlMajorVersion](15) +--[@tSQLt:MaxSqlMajorVersion](15) +CREATE PROCEDURE Private_GetSQLProductMajorVersionTests.[test return correct version on SQL Version = 15] +AS +BEGIN + DECLARE @Version INT; + EXEC @Version = tSQLt.Private_GetSQLProductMajorVersion; + + EXEC tSQLt.AssertEquals @Expected = 15, @Actual = @Version; +END; diff --git a/Tests/Private_HandleMessageAndResultTests.class.sql b/Tests/Private_HandleMessageAndResultTests.class.sql new file mode 100644 index 000000000..ca0bef266 --- /dev/null +++ b/Tests/Private_HandleMessageAndResultTests.class.sql @@ -0,0 +1,174 @@ +EXEC tSQLt.NewTestClass 'Private_HandleMessageAndResultTests'; +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns appropriately formatted Message if the first three parameters are null] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (NULL, NULL, NULL, DEFAULT)); + + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @NewMessage in appropriately formatted Message if only @NewMessage is valued] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (NULL, NULL, 'a random message', DEFAULT)); + + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ] || a random message', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @PrevMessage in appropriately formatted Message if only @PrevMessage is valued] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult ('another random message', NULL, NULL, DEFAULT)); + + EXEC tSQLt.AssertEqualsString @Expected = 'another random message [Result: ] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns appropriate Message if only @PrevResult is valued] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (NULL, 'ResultOne', NULL, DEFAULT)); + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ResultOne] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns in Message for each of the first three parameters if it is an empty string] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult ('', '', '', DEFAULT)); + + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns empty Message if the first three parameters are white space only strings] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (CHAR(9), CHAR(9)+' '+CHAR(9), ' '+CHAR(9)+' ', DEFAULT)); + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @NewResult as Result] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomeResult'',42)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, DEFAULT, DEFAULT, 'SomeResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @PrevResult as Result if @NewResult is less severe] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomeSevereResult'',7),(''SomeLessSevereResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, 'SomeSevereResult', DEFAULT, 'SomeLessSevereResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeSevereResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @PrevResult as Result if @NewResult is less severe if there are other values in Private_Results] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''aaa'',10),(''SomeSevereResult'',7),(''SomeLessSevereResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, 'SomeSevereResult', DEFAULT, 'SomeLessSevereResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeSevereResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @PrevResult if @NewResult is not known] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomePreviousResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, 'SomePreviousResult', DEFAULT, 'SomeUnknownResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomePreviousResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @NewResult if @PrevResult is not known] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomeNewResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, 'SomeUnknownResult', DEFAULT, 'SomeNewResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeNewResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns only the @NewMessage if @PrevMessage is empty and @PrevResult is Success] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult ('', 'Success', 'this is the new message', DEFAULT)); + EXEC tSQLt.AssertEqualsString @Expected = 'this is the new message', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns only the @NewMessage if @PrevMessage is NULL and @PrevResult is Success] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (NULL, 'Success', 'this is the new message', DEFAULT)); + EXEC tSQLt.AssertEqualsString @Expected = 'this is the new message', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @NewResult if @PrevResult is NULL] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomeNewResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, NULL, DEFAULT, 'SomeNewResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeNewResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + diff --git a/Tests/Private_InitTests.class.sql b/Tests/Private_InitTests.class.sql new file mode 100644 index 000000000..662db364b --- /dev/null +++ b/Tests/Private_InitTests.class.sql @@ -0,0 +1,41 @@ +EXEC tSQLt.NewTestClass 'Private_InitTests'; +GO +CREATE FUNCTION Private_InitTests.[mismatching ClrVersions]() +RETURNS TABLE +AS +RETURN SELECT '1234' Version, '4567' ClrVersion,CAST(98.76 AS NUMERIC(10,2)) SqlVersion, CAST(98.76 AS NUMERIC(10,2)) InstalledOnSqlVersion, NULL SqlBuild, CAST(NULL AS NVARCHAR(MAX)) SqlEdition; +GO +CREATE PROCEDURE Private_InitTests.[test Private_Init fails if ClrVersion does not match] +AS +BEGIN + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Info', @FakeFunctionName = 'Private_InitTests.[mismatching ClrVersions]'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt is in an invalid state. Please reinstall tSQLt.', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.Private_Init; + +END; +GO + +CREATE FUNCTION Private_InitTests.[mismatching SqlVersions]() +RETURNS TABLE +AS +RETURN SELECT CAST(98.76 AS NUMERIC(10,2)) SqlVersion, CAST(77.53 AS NUMERIC(10,2)) InstalledOnSqlVersion,'1234' Version, '1234' ClrVersion, NULL SqlBuild, CAST(NULL AS NVARCHAR(MAX)) SqlEdition; +GO +CREATE PROCEDURE Private_InitTests.[test Private_Init fails if SqlVersions do not match] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.EnableExternalAccess'; + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Info', @FakeFunctionName = 'Private_InitTests.[mismatching SqlVersions]'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt is in an invalid state. Please reinstall tSQLt.', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.Private_Init; + +END; +GO + +CREATE FUNCTION Private_InitTests.[matching versions]() +RETURNS TABLE +AS +RETURN SELECT '1234' Version, '1234' ClrVersion,CAST(98.76 AS NUMERIC(10,2)) SqlVersion, CAST(98.76 AS NUMERIC(10,2)) InstalledOnSqlVersion, NULL SqlBuild, CAST(NULL AS NVARCHAR(MAX)) SqlEdition; +GO diff --git a/Tests/Private_InstallationInfoTests.class.sql b/Tests/Private_InstallationInfoTests.class.sql new file mode 100644 index 000000000..467561f86 --- /dev/null +++ b/Tests/Private_InstallationInfoTests.class.sql @@ -0,0 +1,30 @@ +EXEC tSQLt.NewTestClass 'Private_InstallationInfoTests'; +GO +CREATE PROCEDURE Private_InstallationInfoTests.[test returns current SqlVersion] +AS +BEGIN + SELECT SqlVersion INTO #Actual FROM tSQLt.Private_InstallationInfo(); + + SELECT SqlVersion INTO #Expected FROM tSQLt.Info(); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE FUNCTION Private_InstallationInfoTests.[return 98.76]() +RETURNS TABLE +AS +RETURN SELECT CAST(98.76 AS NUMERIC(10,2)) AS SqlVersion; +GO +CREATE PROCEDURE Private_InstallationInfoTests.[test returns SqlVersion from tSQLt.Info after new install] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Info', @FakeFunctionName = 'Private_InstallationInfoTests.[return 98.76]'; + EXEC tSQLt.Private_CreateInstallationInfo; + SELECT SqlVersion INTO #Actual FROM tSQLt.Private_InstallationInfo(); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(98.76); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO diff --git a/Tests/Private_MarktSQLtTempObjectTests.class.sql b/Tests/Private_MarktSQLtTempObjectTests.class.sql new file mode 100644 index 000000000..097aa710e --- /dev/null +++ b/Tests/Private_MarktSQLtTempObjectTests.class.sql @@ -0,0 +1,213 @@ +EXEC tSQLt.NewTestClass 'Private_MarktSQLtTempObjectTests'; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName NVARCHAR(MAX), + @ObjectType NVARCHAR(MAX), + @NewNameOfOriginalObject NVARCHAR(MAX) = 'ARandomString' +AS +BEGIN + EXEC tSQLt.Private_MarktSQLtTempObject + @ObjectName = @ObjectName, + @ObjectType = @ObjectType, + @NewNameOfOriginalObject = @NewNameOfOriginalObject; + + SELECT name, CAST(value AS NVARCHAR(MAX)) value + INTO #Actual + FROM sys.extended_properties + WHERE class_desc = 'OBJECT_OR_COLUMN' + AND major_id = OBJECT_ID(@ObjectName) + AND name IN ('tSQLt.IsTempObject', 'tSQLt.Private_TestDouble_OrgObjectName') + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('tSQLt.IsTempObject', '1'); + INSERT INTO #Expected SELECT 'tSQLt.Private_TestDouble_OrgObjectName',@NewNameOfOriginalObject WHERE @NewNameOfOriginalObject IS NOT NULL; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark a table] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT NOT NULL); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempTable1', + @ObjectType = N'TABLE'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark a stored procedure] +AS +BEGIN + EXEC('CREATE PROCEDURE Private_MarktSQLtTempObjectTests.TempProcedure1 AS RETURN;'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempProcedure1', + @ObjectType = N'PROCEDURE'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark a TV function] +AS +BEGIN + EXEC('CREATE FUNCTION Private_MarktSQLtTempObjectTests.TempTVFunction1() RETURNS @X TABLE (i INT) AS BEGIN RETURN; END;'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempTVFunction1', + @ObjectType = N'FUNCTION'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark a ITV function] +AS +BEGIN + EXEC('CREATE FUNCTION Private_MarktSQLtTempObjectTests.TempITVFunction1() RETURNS TABLE AS RETURN SELECT 1 X;'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempITVFunction1', + @ObjectType = N'FUNCTION'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark a SV function] +AS +BEGIN + EXEC('CREATE FUNCTION Private_MarktSQLtTempObjectTests.TempSVFunction1() RETURNS INT AS BEGIN RETURN NULL; END;'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempSVFunction1', + @ObjectType = N'FUNCTION'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark a constraint] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT CONSTRAINT aSimpleTableConstraint CHECK(i > 0)); + + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = 'Private_MarktSQLtTempObjectTests.aSimpleTableConstraint', + @ObjectType = N'CONSTRAINT'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark a trigger] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT); + EXEC('CREATE TRIGGER Private_MarktSQLtTempObjectTests.TempTrigger ON Private_MarktSQLtTempObjectTests.TempTable1 FOR INSERT AS RETURN;'); + + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempTrigger', + @ObjectType = N'TRIGGER'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test tSQLt.IsTempObject Data Type is BIT] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT NOT NULL); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempTable1', + @ObjectType = N'TABLE'; + + SELECT UPPER(CAST(SQL_VARIANT_PROPERTY(EP.value,'BaseType') AS NVARCHAR(MAX))) IsTempObject_DataType + INTO #Actual + FROM sys.extended_properties AS EP + WHERE EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = OBJECT_ID('Private_MarktSQLtTempObjectTests.TempTable1') + AND EP.name = 'tSQLt.IsTempObject'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('BIT'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test doesn't set tSQLt.Private_TestDouble_OrgObjectName if @NewNameOfOriginalObject is NULL] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT NOT NULL); + EXEC tSQLt.Private_MarktSQLtTempObject + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempTable1', + @ObjectType = N'TABLE', + @NewNameOfOriginalObject = NULL; + + SELECT * + INTO #Actual + FROM sys.extended_properties AS EP + WHERE EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = OBJECT_ID('Private_MarktSQLtTempObjectTests.TempTable1') + AND EP.name = 'tSQLt.Private_TestDouble_OrgObjectName'; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test doesn't set tSQLt.Private_TestDouble_OrgObjectName if @NewNameOfOriginalObject is NULL for child objects] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT NOT NULL CONSTRAINT TempConstraint1 PRIMARY KEY); + EXEC tSQLt.Private_MarktSQLtTempObject + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempConstraint1', + @ObjectType = N'CONSTRAINT', + @NewNameOfOriginalObject = NULL; + + SELECT * + INTO #Actual + FROM sys.extended_properties AS EP + WHERE EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = OBJECT_ID('Private_MarktSQLtTempObjectTests.TempConstraint1') + AND EP.name = 'tSQLt.Private_TestDouble_OrgObjectName'; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test defaults to not setting tSQLt.Private_TestDouble_OrgObjectName] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT NOT NULL); + EXEC tSQLt.Private_MarktSQLtTempObject + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempTable1', + @ObjectType = N'TABLE'; + + SELECT * + INTO #Actual + FROM sys.extended_properties AS EP + WHERE EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = OBJECT_ID('Private_MarktSQLtTempObjectTests.TempTable1') + AND EP.name = 'tSQLt.Private_TestDouble_OrgObjectName'; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark an object with a schema and object name which include single quotes] +AS +BEGIN + EXEC('CREATE SCHEMA [Private_Mark''tSQLtTempObjectTests];'); + EXEC('CREATE PROCEDURE [Private_Mark''tSQLtTempObjectTests].[TempProcedure''1] AS RETURN;'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = '[Private_Mark''tSQLtTempObjectTests].[TempProcedure''1]', + @ObjectType = N'PROCEDURE'; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark an object with a schema and object name which includes a single quotes, spaces, and dots] +AS +BEGIN + EXEC('CREATE SCHEMA [P.rivate_Mark''tSQLtTempObj ectTests];'); + EXEC('CREATE PROCEDURE [P.rivate_Mark''tSQLtTempObj ectTests].[Tem.pPr ocedure''1] AS RETURN;'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = '[P.rivate_Mark''tSQLtTempObj ectTests].[Tem.pPr ocedure''1]', + @ObjectType = N'PROCEDURE'; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark a table with a schema and object name which includes a single quotes, spaces, and dots] +AS +BEGIN + EXEC('CREATE SCHEMA [P.rivate_Mark''tSQLtTempObj ectTests];'); + EXEC('CREATE TABLE [P.rivate_Mark''tSQLtTempObj ectTests].[Tem.pPr ocedure''1] (AA INT);'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = '[P.rivate_Mark''tSQLtTempObj ectTests].[Tem.pPr ocedure''1]', + @ObjectType = N'TABLE', + @NewNameOfOriginalObject = NULL +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + + + diff --git a/Tests/Private_NoTransactionHandleTableTests.class.sql b/Tests/Private_NoTransactionHandleTableTests.class.sql new file mode 100644 index 000000000..2cdd7cdfb --- /dev/null +++ b/Tests/Private_NoTransactionHandleTableTests.class.sql @@ -0,0 +1,659 @@ +EXEC tSQLt.NewTestClass 'Private_NoTransactionHandleTableTests'; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test errors if Action is not an acceptable value] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT); + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Invalid @Action parameter value.%', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Unexpected Action', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test creates new table and saves its name in #TableBackupLog if Action is Save] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + DECLARE @BackupName NVARCHAR(MAX) = (SELECT BackupName FROM #TableBackupLog WHERE OriginalName = 'Private_NoTransactionHandleTableTests.Table1'); + + EXEC tSQLt.AssertEqualsTableSchema @Expected = 'Private_NoTransactionHandleTableTests.Table1', @Actual = @BackupName; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test calls tSQLt.Private_MarktSQLtTempObject on new object] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + TRUNCATE TABLE tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog;--Quirkiness of testing the framework that you use to run the test + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + DECLARE @BackupName NVARCHAR(MAX) = (SELECT BackupName FROM #TableBackupLog WHERE OriginalName = 'Private_NoTransactionHandleTableTests.Table1'); + + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(ISNULL(@BackupName,'Backup table not found.'), N'TABLE', NULL); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not create a backup table if @Action is Save and the @TableAction is Truncate] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Truncate'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not create a backup table if @Action is Save and the @TableAction is Ignore] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Ignore'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test calls tSQLt.RemoveObject if @Action is Save and @TableAction is Hide] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.RemoveObject'; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Hide'; + + SELECT ObjectName INTO #Actual FROM tSQLt.RemoveObject_SpyProcedureLog; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('Private_NoTransactionHandleTableTests.Table1'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not create a backup table if @Action is Save and @TableAction is Hide] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + DECLARE @OriginalObjectId INT = OBJECT_ID('Private_NoTransactionHandleTableTests.Table1'); + + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables WHERE object_id <> @OriginalObjectId; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Hide'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables WHERE object_id <> @OriginalObjectId + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables WHERE object_id <> @OriginalObjectId + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test errors if @Action is Save and @TableAction is not an acceptable value] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.SomeTable(i INT); + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Invalid @TableAction parameter value.%', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.SomeTable', @TableAction = 'Unacceptable'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +--[@tSQLt:MaxSqlMajorVersion](13) +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test augments any internal error with ' tSQLt is in an unknown state: Stopping execution. (<=2016)'] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.SomeTable(i INT); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.RemoveObject', @CommandToExecute='RAISERROR(''SOME INTERNAL ERROR.'',15,11)'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt is in an unknown state: Stopping execution. (SOME INTERNAL ERROR. | Procedure: RemoveObject | Line: 1)', @ExpectedSeverity = 15, @ExpectedState = 11; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.SomeTable', @TableAction = 'Hide'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test augments any internal error with ' tSQLt is in an unknown state: Stopping execution. (>2016)'] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.SomeTable(i INT); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.RemoveObject', @CommandToExecute='RAISERROR(''SOME INTERNAL ERROR.'',15,11)'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt is in an unknown state: Stopping execution. (SOME INTERNAL ERROR. | Procedure: tSQLt.RemoveObject | Line: 1)', @ExpectedSeverity = 15, @ExpectedState = 11; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.SomeTable', @TableAction = 'Hide'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test errors if the table does not exist] +AS +BEGIN + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Nonexistent.Table does not exist%'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'someaction', @FullTableName = 'Nonexistent.Table', @TableAction = 'sometableaction'; +END; +GO +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test copies data from backup into the emptied original table if @Action is Reset and @TableAction is Restore] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + TRUNCATE TABLE Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test deletes original table data before restoring] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + TRUNCATE TABLE Private_NoTransactionHandleTableTests.Table1; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(4, 'abcdef'),(6, 'dd'),(7, 'khdf'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test can restore table with identity column] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES('a'),('bb'),('cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test can restore table with computed column] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, computedcolumn AS UPPER(col1), col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(1,'a'),(2,'bb'),(3,'cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT Id, col1 INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test Reset @Action with unknown @TableAction causes error] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.SomeTable(i INT); + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Invalid @TableAction parameter value.%', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.SomeTable', @TableAction = 'Unacceptable'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test Reset @Action with truncate @TableAction deletes all data from the table] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES('a'),('bb'),('cdce'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Truncate'; + + EXEC tSQLt.AssertEmptyTable @TableName = 'Private_NoTransactionHandleTableTests.Table1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not change the data if @Action is Reset and @TableAction Ignore] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(4, 'jkdf'),(5, 'adfad'),(6, 'yuio'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Ignore'; + + SELECT TOP(0) A.* INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1 A RIGHT JOIN Private_NoTransactionHandleTableTests.Table1 X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'),(4, 'jkdf'),(5, 'adfad'),(6, 'yuio'); + + EXEC tSQLt.AssertEqualsTable @Expected = '#Expected', @Actual = 'Private_NoTransactionHandleTableTests.Table1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not error and does not restore if @Action is Reset and @TableAction Hide] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Hide'; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Hide'; + + SELECT * INTO #Actual FROM sys.tables WHERE object_id = OBJECT_ID('Private_NoTransactionHandleTableTests.Table1'); + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save: the second Save does nothing] +AS +BEGIN + /* No additional temp objects are created, and any data in the backup table is unchanged (even if deleted for example) */ + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save: the second Save does nothing to the existing backup table] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + DECLARE @OriginalBackupTableName NVARCHAR(MAX) = (SELECT BackupName FROM #TableBackupLog WHERE OriginalName = '[Private_NoTransactionHandleTableTests].[Table1]'); + EXEC('DELETE FROM '+@OriginalBackupTableName+';'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + + EXEC tSQLt.AssertEmptyTable @TableName = @OriginalBackupTableName; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[Eclipse #TableBackupLog and execute Save & Reset] +AS +BEGIN + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save(Eclipsed), Reset(Eclipsed), Reset: gets back to point A] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + /*Point A*/ + SELECT * INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1; + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC Private_NoTransactionHandleTableTests.[Eclipse #TableBackupLog and execute Save & Reset]; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save, Reset, Reset: gets back to point A] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + /*Point A*/ + SELECT * INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1; + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[Eclipse #TableBackupLog and execute Save] +AS +BEGIN + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save(Eclipsed), Reset: gets back to point A] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + /*Point A*/ + SELECT * INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1; + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC Private_NoTransactionHandleTableTests.[Eclipse #TableBackupLog and execute Save]; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Reset, Reset: gets back to point A] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + /*Point A*/ + SELECT * INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1; + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Reset: removes entry from #TableBackupLog] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + INSERT INTO #TableBackupLog VALUES ('SomeUnrelatedTable','tSQLt_SomeUnrelatedTable_Temp'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT TOP(0) A.* INTO #Expected FROM #TableBackupLog A RIGHT JOIN #TableBackupLog X ON 1=0; + + INSERT INTO #Expected VALUES ('SomeUnrelatedTable','tSQLt_SomeUnrelatedTable_Temp'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#TableBackupLog'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Hide, @Action Save, and table has be previously hidden, do nothing] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + + EXEC tSQLt.RemoveObject @ObjectName = '[Private_NoTransactionHandleTableTests].[Table1]'; /* Hide here */ + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Truncate, @Action Save, Save, Reset, Save, Reset, Reset: table is empty] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (4,'e'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (5,'d'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (6,'f'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (7,'t'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (8,'g'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + + EXEC tSQLt.AssertEmptyTable @TableName = 'Private_NoTransactionHandleTableTests.Table1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Ignore, @Action Save, Save, Reset, Reset: table is unaltered] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Ignore'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (4,'e'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Ignore'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (6,'f'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Ignore'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (7,'t'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Ignore'; + + SELECT TOP(0) A.* INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1 A RIGHT JOIN Private_NoTransactionHandleTableTests.Table1 X ON 1=0; + + INSERT INTO #Expected VALUES (2,'c'), (3,'a'), (4,'e'), (6,'f'), (7,'t'); + + EXEC tSQLt.AssertEqualsTable @Expected ='#Expected', @Actual = 'Private_NoTransactionHandleTableTests.Table1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Hide, @Action Save, Save: do nothing on second save] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Hide, @Action Save, Save, Reset, Reset: does nothing in total] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + EXEC tSQLt.UndoTestDoubles; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + diff --git a/Tests/Private_NoTransactionHandleTablesTests.class.sql b/Tests/Private_NoTransactionHandleTablesTests.class.sql new file mode 100644 index 000000000..977e8b348 --- /dev/null +++ b/Tests/Private_NoTransactionHandleTablesTests.class.sql @@ -0,0 +1,152 @@ +EXEC tSQLt.NewTestClass 'Private_NoTransactionHandleTablesTests'; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test does not call tSQLt.Private_NoTransactionHandleTable if tSQLt.Private_NoTransactionTableAction is empty] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + + EXEC tSQLt.AssertEmptyTable @TableName = 'tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog'; + +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test calls tSQLt.Private_NoTransactionHandleTable if tSQLt.Private_NoTransactionTableAction contains a table] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl1'',''Restore'');') + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + + SELECT FullTableName INTO #Actual FROM tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('tbl1'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test calls tSQLt.Private_NoTransactionHandleTable for each table in tSQLt.Private_NoTransactionTableAction] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl1'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl2'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl3'',''Restore'');') + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + + SELECT FullTableName INTO #Actual FROM tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('tbl1'),('tbl2'),('tbl3'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test does not call tSQLt.Private_NoTransactionHandleTable for 'Ignore' tables] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl1'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl2'',''Ignore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl3'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl4'',''Other'');') + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + + SELECT FullTableName INTO #Actual FROM tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('tbl1'),('tbl3'),('tbl4'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test passes @Action to tSQLt.Private_NoTransactionHandleTable for each table] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl1'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl2'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl3'',''Restore'');') + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='A Specific Action'; + + SELECT FullTableName, Action INTO #Actual FROM tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('tbl1', 'A Specific Action'),('tbl2', 'A Specific Action'),('tbl3', 'A Specific Action'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test is rerunnable (though it still needs some help from UndoTestDoubles)] +AS +BEGIN + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Save'; + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test combination of Private_NoTransactionHandleTables and UndoTestDoubles is rerunnable] +AS +BEGIN + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Save'; + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + EXEC tSQLt.UndoTestDoubles; + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/Private_NoTransactionTableActionTests.class.sql b/Tests/Private_NoTransactionTableActionTests.class.sql new file mode 100644 index 000000000..35850d49a --- /dev/null +++ b/Tests/Private_NoTransactionTableActionTests.class.sql @@ -0,0 +1,33 @@ +EXEC tSQLt.NewTestClass 'Private_NoTransactionTableActionTests'; +GO +CREATE PROCEDURE Private_NoTransactionTableActionTests.[test contains all tSQLt tables] +AS +BEGIN + SELECT Name INTO #Actual FROM tSQLt.Private_NoTransactionTableAction; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + SELECT QUOTENAME(SCHEMA_NAME(t.schema_id))+'.'+QUOTENAME(t.name) + FROM sys.tables t WHERE schema_id = SCHEMA_ID('tSQLt') AND name NOT LIKE ('%SpyProcedureLog'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionTableActionTests.[test has the correct actions for all tSQLt tables] +AS +BEGIN + SELECT Name, Action INTO #Actual FROM tSQLt.Private_NoTransactionTableAction; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + SELECT '[tSQLt].[Private_NewTestClassList]','Hide' UNION ALL + SELECT '[tSQLt].[Run_LastExecution]', 'Hide' UNION ALL + SELECT '[tSQLt].[Private_Configurations]', 'Restore' UNION ALL + SELECT '[tSQLt].[CaptureOutputLog]', 'Truncate' UNION ALL + SELECT '[tSQLt].[Private_RenamedObjectLog]','Ignore' UNION ALL + SELECT '[tSQLt].[Private_Seize]','Ignore' UNION ALL + SELECT '[tSQLt].[Private_Seize_NoTruncate]','Ignore' UNION ALL + SELECT '[tSQLt].[TestResult]', 'Restore'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO \ No newline at end of file diff --git a/Tests/Private_NullCellTableTests.class.sql b/Tests/Private_NullCellTableTests.class.sql deleted file mode 100644 index 6058cc2cd..000000000 --- a/Tests/Private_NullCellTableTests.class.sql +++ /dev/null @@ -1,60 +0,0 @@ - EXEC tSQLt.NewTestClass 'Private_NullCellTableTests'; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test table contains a single null cell] - AS - BEGIN - CREATE TABLE Private_NullCellTableTests.Expected (I INT); - INSERT INTO Private_NullCellTableTests.Expected(I) VALUES (NULL); - - EXEC tSQLt.AssertEqualsTable 'Private_NullCellTableTests.Expected', 'tSQLt.Private_NullCellTable'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] - @Statement NVARCHAR(MAX) - AS - BEGIN - CREATE TABLE Private_NullCellTableTests.Expected (I INT); - INSERT INTO Private_NullCellTableTests.Expected(I) VALUES (NULL); - - BEGIN TRY - EXEC @Statement; - END TRY - BEGIN CATCH - -- Left intentionally empty - END CATCH; - - EXEC tSQLt.AssertEqualsTable 'Private_NullCellTableTests.Expected', 'tSQLt.Private_NullCellTable'; - - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test cannot insert second NULL row] - AS - BEGIN - EXEC Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] 'INSERT INTO tSQLt.Private_NullCellTable (I) VALUES (NULL);'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test cannot insert a non-NULL row] - AS - BEGIN - EXEC Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] 'INSERT INTO tSQLt.Private_NullCellTable (I) VALUES (5);'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test cannot delete row] - AS - BEGIN - EXEC Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] 'DELETE FROM tSQLt.Private_NullCellTable;'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test cannot update row] - AS - BEGIN - EXEC Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] 'UPDATE tSQLt.Private_NullCellTable SET I = 13;'; - END; - GO - \ No newline at end of file diff --git a/Tests/Private_ProcessTestAnnotationsTests.class.sql b/Tests/Private_ProcessTestAnnotationsTests.class.sql new file mode 100644 index 000000000..aeb83e8ab --- /dev/null +++ b/Tests/Private_ProcessTestAnnotationsTests.class.sql @@ -0,0 +1,212 @@ +EXEC tSQLt.NewTestClass 'Private_ProcessTestAnnotationsTests'; +GO +CREATE FUNCTION Private_ProcessTestAnnotationsTests.[return MyTestAnnotation](@TestObjectId INT) +RETURNS TABLE +AS +RETURN + SELECT 1 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotation]()' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation]()' Annotation; +GO +CREATE FUNCTION Private_ProcessTestAnnotationsTests.[return AnotherTestAnnotation](@TestObjectId INT) +RETURNS TABLE +AS +RETURN + SELECT 1 AnnotationNo,'tSQLt.[@tSQLt:AnotherTestAnnotation]()' EscapedAnnotationString,'tSQLt.[@tSQLt:AnotherTestAnnotation]()' Annotation; +GO +CREATE FUNCTION Private_ProcessTestAnnotationsTests.[return an annotation with a single quote anywhere on line](@TestObjectId INT) +RETURNS TABLE +AS +RETURN + SELECT 1 AnnotationNo,'tSQLt.[@tSQLt:Annotaton](''''aa)' EscapedAnnotationString,'tSQLt.[@tSQLt:Annotation](''aa)' Annotation; +GO +CREATE FUNCTION Private_ProcessTestAnnotationsTests.[return an annotation with an even number of quotes anywhere on line](@TestObjectId INT) +RETURNS TABLE +AS +RETURN + SELECT 1 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotaton](''''aa'''')' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation](''aa'')' Annotation; +GO +CREATE FUNCTION Private_ProcessTestAnnotationsTests.[return an annotation with an odd number of quotes anywhere on line](@TestObjectId INT) +RETURNS TABLE +AS +RETURN + SELECT 1 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotaton](''''aa'''',''''bb'''',''''cc)' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation](''aa'',''bb'',''cc)' Annotation; +GO +CREATE FUNCTION Private_ProcessTestAnnotationsTests.[return several test Annotations with unmatched quotes](@TestObjectId INT) +RETURNS TABLE +AS +RETURN + SELECT 1 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotation1]()' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation1](''aa'')' Annotation + UNION ALL + SELECT 2 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotation2]()' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation2](''bb'',''cc)' Annotation + UNION ALL + SELECT 3 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotation3]()' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation3](''dd'',''ee'')' Annotation + UNION ALL + SELECT 4 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotation4]()' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation4](''ff'',''gg'',''hh)' Annotation + UNION ALL + SELECT 5 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotation5]()' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation5](''ii'',''jj'',''kk'')' Annotation; +GO +CREATE FUNCTION Private_ProcessTestAnnotationsTests.[return 3 Test Annotations](@TestObjectId INT) +RETURNS TABLE +AS +RETURN + SELECT 1 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotation1]()' EscapedAnnotationString, 'tSQLt.[@tSQLt:MyTestAnnotation1]()' Annotation + UNION ALL + SELECT 2 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotation2]()' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation2]()' Annotation + UNION ALL + SELECT 3 AnnotationNo,'tSQLt.[@tSQLt:MyTestAnnotation3]()' EscapedAnnotationString,'tSQLt.[@tSQLt:MyTestAnnotation3]()' Annotation; +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.CreateMyTestAnnotations +AS +BEGIN + EXEC('CREATE PROC tSQLt.[@tSQLt:MyTestAnnotationHelper] AS RETURN 0;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.[@tSQLt:MyTestAnnotationHelper]'; + + EXEC('CREATE PROC tSQLt.[@tSQLt:AnotherTestAnnotationHelper] AS RETURN 0;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.[@tSQLt:AnotherTestAnnotationHelper]'; + + EXEC('CREATE FUNCTION tSQLt.[@tSQLt:MyTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:MyTestAnnotationHelper];'' AS AnnotationCmd;'); + EXEC('CREATE FUNCTION tSQLt.[@tSQLt:AnotherTestAnnotation]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:AnotherTestAnnotationHelper];'' AS AnnotationCmd;'); +END +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.Create3DifferentTestAnnotations + @CommandToExecute NVARCHAR(MAX) = NULL +AS +BEGIN + EXEC('CREATE PROC tSQLt.[@tSQLt:MyTestAnnotation1Helper] AS RETURN 0;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.[@tSQLt:MyTestAnnotation1Helper]', @CommandToExecute = @CommandToExecute; + EXEC('CREATE PROC tSQLt.[@tSQLt:MyTestAnnotation2Helper] AS RETURN 0;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.[@tSQLt:MyTestAnnotation2Helper]', @CommandToExecute = @CommandToExecute; + EXEC('CREATE PROC tSQLt.[@tSQLt:MyTestAnnotation3Helper] AS RETURN 0;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.[@tSQLt:MyTestAnnotation3Helper]', @CommandToExecute = @CommandToExecute; + EXEC('CREATE FUNCTION tSQLt.[@tSQLt:MyTestAnnotation1]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:MyTestAnnotation1Helper];'' AS AnnotationCmd;'); + EXEC('CREATE FUNCTION tSQLt.[@tSQLt:MyTestAnnotation2]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:MyTestAnnotation2Helper];'' AS AnnotationCmd;'); + EXEC('CREATE FUNCTION tSQLt.[@tSQLt:MyTestAnnotation3]() RETURNS TABLE AS RETURN SELECT ''EXEC tSQLt.[@tSQLt:MyTestAnnotation3Helper];'' AS AnnotationCmd;'); + +END +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.[test calls annotation procedure] +AS +BEGIN + EXEC Private_ProcessTestAnnotationsTests.CreateMyTestAnnotations; + EXEC tSQLt.FakeFunction + @FunctionName = 'tSQLt.Private_ListTestAnnotations', + @FakeFunctionName = 'Private_ProcessTestAnnotationsTests.[return MyTestAnnotation]'; + + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId = NULL; + + SELECT 1 WasCalled INTO #Actual FROM tSQLt.[@tSQLt:MyTestAnnotationHelper_SpyProcedureLog]; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.[test calls AnotherTestAnnotation] +AS +BEGIN + EXEC Private_ProcessTestAnnotationsTests.CreateMyTestAnnotations; + EXEC tSQLt.FakeFunction + @FunctionName = 'tSQLt.Private_ListTestAnnotations', + @FakeFunctionName = 'Private_ProcessTestAnnotationsTests.[return AnotherTestAnnotation]'; + + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId = NULL; + + SELECT 1 WasCalled INTO #Actual FROM tSQLt.[@tSQLt:AnotherTestAnnotationHelper_SpyProcedureLog]; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.[test calls all annotation procedures] +AS +BEGIN + EXEC Private_ProcessTestAnnotationsTests.Create3DifferentTestAnnotations; + EXEC tSQLt.FakeFunction + @FunctionName = 'tSQLt.Private_ListTestAnnotations', + @FakeFunctionName = 'Private_ProcessTestAnnotationsTests.[return 3 Test Annotations]'; + + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId = NULL; + + SELECT * + INTO #Actual + FROM + ( + SELECT 'MyTestAnnotation1' WasCalled FROM tSQLt.[@tSQLt:MyTestAnnotation1Helper_SpyProcedureLog] + UNION ALL + SELECT 'MyTestAnnotation2' WasCalled FROM tSQLt.[@tSQLt:MyTestAnnotation2Helper_SpyProcedureLog] + UNION ALL + SELECT 'MyTestAnnotation3' WasCalled FROM tSQLt.[@tSQLt:MyTestAnnotation3Helper_SpyProcedureLog] + )X; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('MyTestAnnotation1'),('MyTestAnnotation2'),('MyTestAnnotation3'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.[test calls all annotation procedures in order] +AS +BEGIN + EXEC Private_ProcessTestAnnotationsTests.Create3DifferentTestAnnotations 'INSERT INTO #Actual VALUES(OBJECT_NAME(@@PROCID));'; + EXEC tSQLt.FakeFunction + @FunctionName = 'tSQLt.Private_ListTestAnnotations', + @FakeFunctionName = 'Private_ProcessTestAnnotationsTests.[return 3 Test Annotations]'; + + CREATE TABLE #Actual(CallOrder INT IDENTITY(1,1),Annotation NVARCHAR(MAX)); + + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId = NULL; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'@tSQLt:MyTestAnnotation1Helper'),(2,'@tSQLt:MyTestAnnotation2Helper'),(3,'@tSQLt:MyTestAnnotation3Helper'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.[test reports meaningful error if annotation line has a lonely single quote] +AS +BEGIN + EXEC tSQLt.FakeFunction + @FunctionName = 'tSQLt.Private_ListTestAnnotations', + @FakeFunctionName = 'Private_ProcessTestAnnotationsTests.[return an annotation with a single quote anywhere on line]'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Annotation has unmatched quote: tSQLt.[@tSQLt:Annotation](''aa)', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId = NULL; +END; +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.[test reports no error if annotation line has matched quotes] +AS +BEGIN + + EXEC('CREATE FUNCTION tSQLt.[@tSQLt:MyTestAnnotation](@param NVARCHAR(MAX)) RETURNS TABLE AS RETURN SELECT ''DECLARE @x XML;'' AS AnnotationCmd;'); + + EXEC tSQLt.FakeFunction + @FunctionName = 'tSQLt.Private_ListTestAnnotations', + @FakeFunctionName = 'Private_ProcessTestAnnotationsTests.[return an annotation with an even number of quotes anywhere on line]'; + + EXEC tSQLt.ExpectNoException; + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId = NULL; +END; +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.[test reports error if annotation line has an odd number of quotes] +AS +BEGIN + + EXEC tSQLt.FakeFunction + @FunctionName = 'tSQLt.Private_ListTestAnnotations', + @FakeFunctionName = 'Private_ProcessTestAnnotationsTests.[return an annotation with an odd number of quotes anywhere on line]'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Annotation has unmatched quote: tSQLt.[@tSQLt:MyTestAnnotation](''aa'',''bb'',''cc)', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId = NULL; +END; +GO +CREATE PROCEDURE Private_ProcessTestAnnotationsTests.[test reports unmatched quote error for correct annotation] +AS +BEGIN + + EXEC tSQLt.FakeFunction + @FunctionName = 'tSQLt.Private_ListTestAnnotations', + @FakeFunctionName = 'Private_ProcessTestAnnotationsTests.[return several test Annotations with unmatched quotes]'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Annotation has unmatched quote: tSQLt.[@tSQLt:MyTestAnnotation2](''bb'',''cc)', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId = NULL; +END; +GO + + + + diff --git a/Tests/Private_RemoveSchemaBindingTests.class.sql b/Tests/Private_RemoveSchemaBindingTests.class.sql new file mode 100644 index 000000000..dca457260 --- /dev/null +++ b/Tests/Private_RemoveSchemaBindingTests.class.sql @@ -0,0 +1,202 @@ + +--> New Direction: Drop schema bound objects and offer option to recreate as needed. + +EXEC tSQLt.NewTestClass 'Private_RemoveSchemaBindingTests'; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[Assert SB property on view is removed and view is still working] + @CreateViewStatement NVARCHAR(MAX) +AS +BEGIN + CREATE TABLE Private_RemoveSchemaBindingTests.T1(C1 INT); + INSERT INTO Private_RemoveSchemaBindingTests.T1(C1) + VALUES(CHECKSUM(NEWID())); + INSERT INTO Private_RemoveSchemaBindingTests.T1(C1) + VALUES(CHECKSUM(NEWID())); + INSERT INTO Private_RemoveSchemaBindingTests.T1(C1) + VALUES(CHECKSUM(NEWID())); + + EXEC(@CreateViewStatement); + + DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemaBindingTests.V1'); + EXEC tSQLt.Private_RemoveSchemaBinding @object_id = @object_id; + + SELECT QUOTENAME(OBJECT_SCHEMA_NAME(SED.referencing_id))+'.'+QUOTENAME(OBJECT_NAME(SED.referencing_id)) AS schema_bound_object_name,SED.referencing_id,SED.referencing_class_desc + INTO #SchemaBoundObjects + FROM sys.sql_expression_dependencies AS SED + WHERE SED.is_schema_bound_reference = 1 + AND SED.referenced_id = OBJECT_ID('Private_RemoveSchemaBindingTests.T1'); + + EXEC tSQLt.AssertEmptyTable @TableName = '#SchemaBoundObjects'; + + SELECT * + INTO Private_RemoveSchemaBindingTests.Actual + FROM Private_RemoveSchemaBindingTests.V1; + + EXEC tSQLt.AssertEqualsTable 'Private_RemoveSchemaBindingTests.T1','Private_RemoveSchemaBindingTests.Actual'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test SB property on view is removed and view is still working] +AS +BEGIN + EXEC Private_RemoveSchemaBindingTests.[Assert SB property on view is removed and view is still working] + 'CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests.T1 AS T;'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test does not remove second W/SB statement] +AS +BEGIN + CREATE TABLE Private_RemoveSchemaBindingTests.T1(C1 INT); + EXEC('CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS SELECT T.C1,''CREATE VIEW dbo.test WITH SCHEMABINDING'' AS C2 FROM Private_RemoveSchemaBindingTests.T1 AS T;'); + INSERT INTO Private_RemoveSchemaBindingTests.T1(C1) + VALUES(42); + + DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemaBindingTests.V1'); + EXEC tSQLt.Private_RemoveSchemaBinding @object_id = @object_id; + + SELECT C1,C2 + INTO #Actual + FROM Private_RemoveSchemaBindingTests.V1; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + VALUES(42,'CREATE VIEW dbo.test WITH SCHEMABINDING'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test handles leading spaces] +AS +BEGIN + EXEC Private_RemoveSchemaBindingTests.[Assert SB property on view is removed and view is still working] + ' CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests.T1 AS T;'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test handles newlines] +AS +BEGIN + EXEC Private_RemoveSchemaBindingTests.[Assert SB property on view is removed and view is still working] + ' + CREATE + VIEW + Private_RemoveSchemaBindingTests.V1 + WITH + SCHEMABINDING + AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests.T1 AS T;'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test handles leading one line comments] +AS +BEGIN + EXEC Private_RemoveSchemaBindingTests.[Assert SB property on view is removed and view is still working] + '-- test + CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests.T1 AS T;'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test handles leading one line comments with CREATE VIEW statement] +AS +BEGIN + EXEC Private_RemoveSchemaBindingTests.[Assert SB property on view is removed and view is still working] + '-- test: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + -- test2: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + -- + CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests.T1 AS T;'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test handles leading multi-line comments with CREATE VIEW statement] +AS +BEGIN + EXEC Private_RemoveSchemaBindingTests.[Assert SB property on view is removed and view is still working] + '/* test: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + -- test2: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + */CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests.T1 AS T;'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test handles leading double nested multi-line comments] +AS +BEGIN + EXEC Private_RemoveSchemaBindingTests.[Assert SB property on view is removed and view is still working] + '/* test: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + /* test: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + */ test: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + */CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests.T1 AS T;'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test handles leading nested multi-line comments and other niceties with CREATE VIEW statement] +AS +BEGIN + EXEC Private_RemoveSchemaBindingTests.[Assert SB property on view is removed and view is still working] + '/* /test: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + /* /test: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + /* /test: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + -- *test2: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + */-- *test2: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + */-- *test2: CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS + */--/* + CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBindingTests.T1 AS T;'; +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBindingTests.[test works on indexed view] +AS +BEGIN + CREATE TABLE Private_RemoveSchemaBindingTests.T1(C1 INT); + EXEC('CREATE VIEW Private_RemoveSchemaBindingTests.V1 WITH SCHEMABINDING AS SELECT T.C1,''A Constant'' AS C2 FROM Private_RemoveSchemaBindingTests.T1 AS T;'); + EXEC('CREATE UNIQUE CLUSTERED INDEX [CI:V1] ON Private_RemoveSchemaBindingTests.V1(C1);'); + EXEC('CREATE NONCLUSTERED INDEX [NCI:V1] ON Private_RemoveSchemaBindingTests.V1(C2);'); + INSERT INTO Private_RemoveSchemaBindingTests.T1(C1) + VALUES(42); + + DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemaBindingTests.V1'); + EXEC tSQLt.Private_RemoveSchemaBinding @object_id = @object_id; + + SELECT C1,C2 + INTO #Actual + FROM Private_RemoveSchemaBindingTests.V1; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + VALUES(42,'A Constant'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO + + +/* +-- Comments before create view including +-- superflous whitespace (CREATE VIEW as well as WITH SCHEMABINDING) +-- indexed views +-- computed columns +-- indexed computed columns +-- other object types: (see remarks) http://msdn.microsoft.com/en-us/library/bb677315.aspx + + + SELECT * FROM sys.sql_expression_dependencies AS SED + JOIN sys.sql_modules AS SM + ON SED.referencing_id = SM.object_id + WHERE SED.referenced_id = OBJECT_ID('Private_RemoveSchemaBindingTests.T1'); + + +-- /* +-- PRINT 1; +-- --/* +-- PRINT 1; +-- /* +-- PRINT 1; +-- --*/ +-- PRINT 1; +-- --*/ +-- PRINT 1; +-- --*/ +-- PRINT 1; + + + + + +--*/ \ No newline at end of file diff --git a/Tests/Private_RemoveSchemaBoundReferencesTests.class.sql b/Tests/Private_RemoveSchemaBoundReferencesTests.class.sql new file mode 100644 index 000000000..8a1f8cd5b --- /dev/null +++ b/Tests/Private_RemoveSchemaBoundReferencesTests.class.sql @@ -0,0 +1,92 @@ +EXEC tSQLt.NewTestClass 'Private_RemoveSchemaBoundReferencesTests'; +GO +CREATE PROCEDURE Private_RemoveSchemaBoundReferencesTests.[test calls RemoveSchemaBinding for single view] +AS +BEGIN + CREATE TABLE Private_RemoveSchemaBoundReferencesTests.T1(C1 INT); + EXEC('CREATE VIEW Private_RemoveSchemaBoundReferencesTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBoundReferencesTests.T1 AS T;'); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RemoveSchemaBinding'; + + DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.T1'); + EXEC tSQLt.Private_RemoveSchemaBoundReferences @object_id = @object_id; + + SELECT object_id + INTO #Actual + FROM tSQLt.Private_RemoveSchemaBinding_SpyProcedureLog; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + VALUES(OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.V1')); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBoundReferencesTests.[test calls RemoveSchemaBinding for multiple views] +AS +BEGIN + CREATE TABLE Private_RemoveSchemaBoundReferencesTests.T1(C1 INT); + EXEC('CREATE VIEW Private_RemoveSchemaBoundReferencesTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBoundReferencesTests.T1 AS T;'); + EXEC('CREATE VIEW Private_RemoveSchemaBoundReferencesTests.V2 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBoundReferencesTests.T1 AS T;'); + EXEC('CREATE VIEW Private_RemoveSchemaBoundReferencesTests.V3 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBoundReferencesTests.T1 AS T;'); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RemoveSchemaBinding'; + + DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.T1'); + EXEC tSQLt.Private_RemoveSchemaBoundReferences @object_id = @object_id; + + SELECT object_id + INTO #Actual + FROM tSQLt.Private_RemoveSchemaBinding_SpyProcedureLog; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + VALUES(OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.V1')); + INSERT INTO #Expected + VALUES(OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.V2')); + INSERT INTO #Expected + VALUES(OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.V3')); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE Private_RemoveSchemaBoundReferencesTests.[test calls RemoveSchemaBinding for multiple recursive views] +AS +BEGIN + CREATE TABLE Private_RemoveSchemaBoundReferencesTests.T1(C1 INT); + EXEC('CREATE VIEW Private_RemoveSchemaBoundReferencesTests.V1 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBoundReferencesTests.T1 AS T;'); + EXEC('CREATE VIEW Private_RemoveSchemaBoundReferencesTests.V2 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBoundReferencesTests.V1 AS T;'); + EXEC('CREATE VIEW Private_RemoveSchemaBoundReferencesTests.V3 WITH SCHEMABINDING AS SELECT T.C1 FROM Private_RemoveSchemaBoundReferencesTests.V2 AS T;'); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RemoveSchemaBinding'; + + DECLARE @object_id INT;SET @object_id = OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.T1'); + EXEC tSQLt.Private_RemoveSchemaBoundReferences @object_id = @object_id; + + SELECT _id_*1 call_order,object_id + INTO #Actual + FROM tSQLt.Private_RemoveSchemaBinding_SpyProcedureLog; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected + VALUES(1, OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.V3')); + INSERT INTO #Expected + VALUES(2, OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.V2')); + INSERT INTO #Expected + VALUES(3, OBJECT_ID('Private_RemoveSchemaBoundReferencesTests.V1')); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO diff --git a/Tests/Private_ResultsTests.class.sql b/Tests/Private_ResultsTests.class.sql new file mode 100644 index 000000000..26460f1a6 --- /dev/null +++ b/Tests/Private_ResultsTests.class.sql @@ -0,0 +1,19 @@ +EXEC tSQLt.NewTestClass 'Private_ResultsTests'; +GO +CREATE PROCEDURE Private_ResultsTests.[test Contains all Result values (double ledger)] +AS +BEGIN + SELECT Severity, Result INTO #Actual FROM tSQLt.Private_Results; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + SELECT 1,'Success' UNION ALL + SELECT 2,'Skipped' UNION ALL + SELECT 3,'Failure' UNION ALL + SELECT 4,'Error' UNION ALL + SELECT 5,'Abort' UNION ALL + SELECT 6,'FATAL' ; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO diff --git a/Tests/Private_ScriptIndexTests.class.sql b/Tests/Private_ScriptIndexTests.class.sql index 9f6b7b5fd..6940d484f 100644 --- a/Tests/Private_ScriptIndexTests.class.sql +++ b/Tests/Private_ScriptIndexTests.class.sql @@ -8,15 +8,31 @@ CREATE PROCEDURE Private_ScriptIndexTests.[assert index is scripted correctly] @index_name NVARCHAR(MAX) = 'Private_ScriptIndexTests.T1 - IDX1' AS BEGIN - EXEC(@setup1); - EXEC(@index_create_cmd); - EXEC(@setup2); + BEGIN TRY + EXEC(@setup1); + EXEC(@index_create_cmd); + EXEC(@setup2); - DECLARE @ScriptedCmd NVARCHAR(MAX); - SELECT @ScriptedCmd = create_cmd - FROM tSQLt.Private_ScriptIndex(OBJECT_ID(@object_name),(SELECT index_id FROM sys.indexes AS I WHERE I.name = @index_name AND I.object_id = OBJECT_ID(@object_name))); + DECLARE @ScriptedCmd NVARCHAR(MAX); + DECLARE @object_id INT = OBJECT_ID(@object_name); + DECLARE @index_id INT = (SELECT index_id FROM sys.indexes AS I WHERE I.name = @index_name AND I.object_id = OBJECT_ID(@object_name)); - EXEC tSQLt.AssertEqualsString @Expected = @index_create_cmd, @Actual = @ScriptedCmd; + SELECT @ScriptedCmd = create_cmd + FROM tSQLt.Private_ScriptIndex(@object_id,@index_id); + + EXEC tSQLt.AssertEqualsString @Expected = @index_create_cmd, @Actual = @ScriptedCmd; + END TRY + BEGIN CATCH + DECLARE + @EL NVARCHAR(MAX) = CAST(ERROR_LINE() AS NVARCHAR(MAX)), + @EM NVARCHAR(MAX) = ERROR_MESSAGE(), + @EN NVARCHAR(MAX) = CAST(ERROR_NUMBER() AS NVARCHAR(MAX)), + @EP NVARCHAR(MAX) = ERROR_PROCEDURE(), + @ES NVARCHAR(MAX) = CAST(ERROR_SEVERITY() AS NVARCHAR(MAX)), + @ET NVARCHAR(MAX) = CAST(ERROR_STATE() AS NVARCHAR(MAX)); + RAISERROR('%s%s%s%s%s%s',16,10,@EN,@EM,@ES,@ET,@EP,@EL)WITH NOWAIT; + END CATCH; + RETURN; END; GO CREATE PROCEDURE Private_ScriptIndexTests.[test scripts simple index] @@ -157,3 +173,43 @@ BEGIN END; GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE Private_ScriptIndexTests.[test handles filter] +AS +BEGIN + EXEC Private_ScriptIndexTests.[assert index is scripted correctly] + @index_create_cmd = 'CREATE UNIQUE NONCLUSTERED INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1]([C1]ASC,[C3]DESC)INCLUDE([C4],[C2])WHERE([C1]=(3));'; +END; +GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE Private_ScriptIndexTests.[test handles hypothetical index] +AS +BEGIN + EXEC Private_ScriptIndexTests.[assert index is scripted correctly] + @index_create_cmd = 'CREATE UNIQUE NONCLUSTERED INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1]([C1]ASC,[C3]DESC)INCLUDE([C4],[C2])WITH(STATISTICS_ONLY = -1);'; +END; +GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE Private_ScriptIndexTests.[test handles hypothetical filtered index] +AS +BEGIN + EXEC Private_ScriptIndexTests.[assert index is scripted correctly] + @index_create_cmd = 'CREATE UNIQUE NONCLUSTERED INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1]([C1]ASC,[C3]DESC)INCLUDE([C4],[C2])WHERE([C1]=(3))WITH(STATISTICS_ONLY = -1);'; +END; +GO +--[@tSQLt:MinSqlMajorVersion](11) +CREATE PROCEDURE Private_ScriptIndexTests.[test handles nonclustered columnstore index] +AS +BEGIN + EXEC Private_ScriptIndexTests.[assert index is scripted correctly] + @index_create_cmd = 'CREATE NONCLUSTERED COLUMNSTORE INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1]([C1],[C3]);'; +END; +GO +--[@tSQLt:MinSqlMajorVersion](12) +CREATE PROCEDURE Private_ScriptIndexTests.[test handles clustered columnstore index] +AS +BEGIN + EXEC Private_ScriptIndexTests.[assert index is scripted correctly] + @index_create_cmd = 'CREATE CLUSTERED COLUMNSTORE INDEX [Private_ScriptIndexTests.T1 - IDX1] ON [Private_ScriptIndexTests].[T1];'; +END; +GO \ No newline at end of file diff --git a/Tests/Private_SeizeTests.class.sql b/Tests/Private_SeizeTests.class.sql new file mode 100644 index 000000000..ae0f18982 --- /dev/null +++ b/Tests/Private_SeizeTests.class.sql @@ -0,0 +1,59 @@ +EXEC tSQLt.NewTestClass 'Private_SeizeTests'; +GO +CREATE PROCEDURE Private_SeizeTests.[test can insert a 1] +AS +BEGIN + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(1); + + SELECT Kaput INTO #Actual FROM tSQLt.Private_Seize; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot insert <> 1] +AS +BEGIN + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%"Private_Seize:CHK"%'; + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(0); + +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot insert NULL] +AS +BEGIN + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%column does not allow nulls%'; + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(NULL); + +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot delete row] +AS +BEGIN + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(1); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'This is a private table that you should not mess with!', @ExpectedSeverity = 16, @ExpectedState = 10; + DELETE FROM tSQLt.Private_Seize; +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot update row (even to the same value)] +AS +BEGIN + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(1); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'This is a private table that you should not mess with!', @ExpectedSeverity = 16, @ExpectedState = 10; + UPDATE tSQLt.Private_Seize SET Kaput = 1; +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot TRUNCATE] +AS +BEGIN + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(1); + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'Cannot truncate table _tSQLt.Private_Seize_%'; + TRUNCATE TABLE tSQLt.Private_Seize; +END; +GO diff --git a/Tests/Private_SplitSqlVersionTests.class.sql b/Tests/Private_SplitSqlVersionTests.class.sql new file mode 100644 index 000000000..40ccb9ad3 --- /dev/null +++ b/Tests/Private_SplitSqlVersionTests.class.sql @@ -0,0 +1,35 @@ +EXEC tSQLt.NewTestClass 'Private_SplitSqlVersionTests'; +GO +CREATE PROCEDURE Private_SplitSqlVersionTests.[test splits a 4-part version correctly] +AS +BEGIN + SELECT Major, + Minor, + Build, + Revision + INTO #Actual + FROM tSQLt.Private_SplitSqlVersion('15.0.1.2345'); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('15','0','1','2345'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_SplitSqlVersionTests.[test works with shorter versions] +AS +BEGIN + SELECT Major, + Minor, + Build, + Revision + INTO #Actual + FROM (VALUES('15.0.1'),('14.2'),('13'))Versions(V) + CROSS APPLY tSQLt.Private_SplitSqlVersion(Versions.V); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('15','0','1',NULL); + INSERT INTO #Expected VALUES('14','2',NULL,NULL); + INSERT INTO #Expected VALUES('13',NULL,NULL,NULL); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO diff --git a/Tests/Private_SqlVariantFormatterTests.class.sql b/Tests/Private_SqlVariantFormatterTests.class.sql index e4e9b897c..87f11cdd4 100644 --- a/Tests/Private_SqlVariantFormatterTests.class.sql +++ b/Tests/Private_SqlVariantFormatterTests.class.sql @@ -1,7 +1,7 @@ EXEC tSQLt.NewTestClass 'Private_SqlVariantFormatterTests'; GO -CREATE PROC Private_SqlVariantFormatterTests.[test formats INT] +CREATE PROCEDURE Private_SqlVariantFormatterTests.[test formats INT] AS BEGIN DECLARE @Actual NVARCHAR(MAX); @@ -11,7 +11,7 @@ BEGIN END; GO -CREATE PROC Private_SqlVariantFormatterTests.[test formats other data types] +CREATE PROCEDURE Private_SqlVariantFormatterTests.[test formats other data types] AS BEGIN CREATE TABLE #Input( @@ -127,3 +127,45 @@ BEGIN EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; END; GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE Private_SqlVariantFormatterTests.[test formats new 2008 data types] +AS +BEGIN + CREATE TABLE #Input( + [DATE] DATE, + [DATETIME2] DATETIME2, + [DATETIMEOFFSET] DATETIMEOFFSET, + [TIME] TIME + ); + INSERT INTO #Input + SELECT + '2013-04-05' AS [DATE], + '2013-04-05T06:07:08.9876543' AS [DATETIME2], + '2013-04-05T06:07:08.9876543+02:01' AS [DATETIMEOFFSET], + '06:07:08.9876543'AS [TIME]; + + CREATE TABLE #Actual( + [DATE] NVARCHAR(MAX), + [DATETIME2] NVARCHAR(MAX), + [DATETIMEOFFSET] NVARCHAR(MAX), + [TIME] NVARCHAR(MAX) + ); + INSERT INTO #Actual + SELECT + tSQLt.Private_SqlVariantFormatter([DATE]) AS [DATE], + tSQLt.Private_SqlVariantFormatter([DATETIME2]) AS [DATETIME2], + tSQLt.Private_SqlVariantFormatter([DATETIMEOFFSET]) AS [DATETIMEOFFSET], + tSQLt.Private_SqlVariantFormatter([TIME]) AS [TIME] + FROM #Input; + + SELECT TOP(0) * INTO #Expected FROM #Actual; + INSERT INTO #Expected + SELECT + '2013-04-05' AS [DATE], + '2013-04-05T06:07:08.9876543' AS [DATETIME2], + '2013-04-05T06:07:08.9876543+02:01' AS [DATETIMEOFFSET], + '06:07:08.9876543' AS [TIME]; + + EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; +END; +GO diff --git a/Tests/tSQLt_test_ResultSetFilter.class.sql b/Tests/ResultSetFilterTests.class.sql similarity index 74% rename from Tests/tSQLt_test_ResultSetFilter.class.sql rename to Tests/ResultSetFilterTests.class.sql index c9e7c0ca8..e8979af41 100644 --- a/Tests/tSQLt_test_ResultSetFilter.class.sql +++ b/Tests/ResultSetFilterTests.class.sql @@ -1,6 +1,6 @@ -EXEC tSQLt.NewTestClass 'tSQLt_test_ResultSetFilter'; +EXEC tSQLt.NewTestClass 'ResultSetFilterTests'; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter returns specified result set] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter returns specified result set] AS BEGIN CREATE TABLE #Actual (val INT); @@ -16,7 +16,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter returns specified result set with multiple columns] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter returns specified result set with multiple columns] AS BEGIN CREATE TABLE #Actual (val1 INT, val2 VARCHAR(3)); @@ -32,7 +32,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter throws error if specified result set is 1 greater than number of result sets returned] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter throws error if specified result set is 1 greater than number of result sets returned] AS BEGIN DECLARE @err NVARCHAR(MAX); SET @err = '--NO Error Thrown!--'; @@ -51,7 +51,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter throws error if specified result set is more than 1 greater than number of result sets returned] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter throws error if specified result set is more than 1 greater than number of result sets returned] AS BEGIN DECLARE @err NVARCHAR(MAX); SET @err = '--NO Error Thrown!--'; @@ -70,7 +70,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter retrieves no records and throws no error if 0 is specified] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter retrieves no records and throws no error if 0 is specified] AS BEGIN CREATE TABLE #Actual (val INT); @@ -83,7 +83,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter retrieves no result set if 0 is specified] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter retrieves no result set if 0 is specified] AS BEGIN DECLARE @err NVARCHAR(MAX); SET @err = '--NO Error Thrown!--'; @@ -102,7 +102,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter handles code not returning a result set] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter handles code not returning a result set] AS BEGIN DECLARE @err NVARCHAR(MAX); SET @err = '--NO Error Thrown!--'; @@ -121,14 +121,14 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter throws no error if code is not returning a result set and 0 is passed in] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter throws no error if code is not returning a result set and 0 is passed in] AS BEGIN EXEC tSQLt.ResultSetFilter 0,'DECLARE @NoOp INT;'; END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter throws error if result set number NULL specified] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter throws error if result set number NULL specified] AS BEGIN DECLARE @err NVARCHAR(MAX); SET @err = '--NO Error Thrown!--'; @@ -148,7 +148,7 @@ END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter throws error if result set number of less than 0 specified] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter throws error if result set number of less than 0 specified] AS BEGIN DECLARE @err NVARCHAR(MAX); SET @err = ''; @@ -167,7 +167,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter can handle each datatype] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter can handle each datatype] AS BEGIN EXEC tSQLt.AssertResultSetsHaveSameMetaData @@ -263,7 +263,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter produces only requested columns when underlying table contains primary key] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter produces only requested columns when underlying table contains primary key] AS BEGIN CREATE TABLE BaseTable (i INT PRIMARY KEY, v VARCHAR(15)); @@ -280,7 +280,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter produces only requested columns when a join on foreign keys is performed] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter produces only requested columns when a join on foreign keys is performed] AS BEGIN CREATE TABLE BaseTable1 (i1 INT PRIMARY KEY, v1 VARCHAR(15)); @@ -300,7 +300,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter produces only requested columns when a unique column exists] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter produces only requested columns when a unique column exists] AS BEGIN CREATE TABLE BaseTable1 (i1 INT UNIQUE, v1 VARCHAR(15)); @@ -317,7 +317,7 @@ BEGIN END; GO -CREATE PROC tSQLt_test_ResultSetFilter.[test ResultSetFilter produces only requested columns when a check constraint exists] +CREATE PROC ResultSetFilterTests.[test ResultSetFilter produces only requested columns when a check constraint exists] AS BEGIN CREATE TABLE BaseTable1 (i1 INT CHECK(i1 = 1), v1 VARCHAR(15)); @@ -333,3 +333,38 @@ BEGIN EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; END; GO +CREATE PROCEDURE ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype + @Value NVARCHAR(MAX), + @Datatype NVARCHAR(MAX) +AS +BEGIN + DECLARE @ExpectedStmt NVARCHAR(MAX), + @ActualStmt NVARCHAR(MAX); + + DECLARE @ActualValue NVARCHAR(MAX); + SET @ActualValue = REPLACE(@Value, '''', ''''''); + + SELECT @ExpectedStmt = 'SELECT CAST(' + @Value + ' AS ' + @Datatype + ') AS val;'; + SELECT @ActualStmt = 'EXEC tSQLt.ResultSetFilter 1, ''SELECT CAST(' + @ActualValue + ' AS ' + @Datatype + ') AS val;'''; + + EXEC tSQLt.AssertResultSetsHaveSameMetaData @ExpectedStmt, @ActualStmt; + +END +GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE ResultSetFilterTests.[test ResultSetFilter can handle each 2008 datatype] +AS +BEGIN + EXEC ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797''', 'DATETIME2'; + EXEC ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797''', 'DATETIME2(3)'; + EXEC ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797 +01:15''', 'DATETIMEOFFSET'; + EXEC ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797 +01:15''', 'DATETIMEOFFSET(3)'; + EXEC ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797''', 'DATE'; + EXEC ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype '''2011-09-27 12:23:47.846753797''', 'TIME'; + + EXEC ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype 'geometry::STGeomFromText(''LINESTRING (100 100, 20 180, 180 180)'', 0)', 'geometry'; + EXEC ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype 'geography::STGeomFromText(''LINESTRING(-122.360 47.656, -122.343 47.656)'', 4326)', 'geography'; + EXEC ResultSetFilterTests.AssertResultSetFilterCanHandleDatatype 'hierarchyid::Parse(''/1/'')', 'hierarchyid'; + +END +GO diff --git a/Tests/Run_Methods_Tests.class.sql b/Tests/Run_Methods_Tests.class.sql index 04e39cdff..0cd337b12 100644 --- a/Tests/Run_Methods_Tests.class.sql +++ b/Tests/Run_Methods_Tests.class.sql @@ -1,5 +1,48 @@ EXEC tSQLt.NewTestClass 'Run_Methods_Tests'; GO +CREATE PROC Run_Methods_Tests.[test all tSQLt.Run methods call the run method handler] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RunMethodHandler'; + + SELECT P.object_id,P.name + INTO #Expected + FROM sys.procedures AS P + WHERE P.schema_id = SCHEMA_ID('tSQLt') + AND P.name LIKE 'Run%' + AND P.name NOT IN('RunTest'); + + SELECT TOP(0) E.* INTO #Actual FROM #Expected E RIGHT JOIN #Expected X ON 1=0; + + DECLARE @cmd NVARCHAR(MAX) = + ( + SELECT 'EXEC tSQLt.'+QUOTENAME(P.name) + PP.ParameterList + ';INSERT INTO #Actual SELECT '+CAST(P.object_id AS NVARCHAR(MAX))+','''+P.name+''' FROM tSQLt.Private_RunMethodHandler_SpyProcedureLog; TRUNCATE TABLE tSQLt.Private_RunMethodHandler_SpyProcedureLog;' + FROM #Expected AS P + OUTER APPLY + ( + SELECT + ISNULL( + STUFF( + ( + SELECT ','+PP.name+'=NULL' FROM sys.parameters AS PP + WHERE P.object_id = PP.object_id + ORDER BY PP.parameter_id + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'), + 1,1,' ' + ), + '' + ) + )PP(ParameterList) + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO + CREATE PROC Run_Methods_Tests.[test Run truncates TestResult table] AS BEGIN @@ -35,49 +78,38 @@ BEGIN END; GO -CREATE PROC Run_Methods_Tests.[test RunTestClass raises error if error in default print mode] +CREATE PROC Run_Methods_Tests.test_Run_handles_test_names_with_spaces AS BEGIN - DECLARE @ErrorRaised INT; SET @ErrorRaised = 0; + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; - EXEC tSQLt.SetTestResultFormatter 'tSQLt.DefaultResultFormatter'; - EXEC('CREATE SCHEMA MyTestClass;'); - EXEC('CREATE PROC MyTestClass.TestCaseA AS RETURN 1/0;'); + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE PROC MyTestClass.[Test Case A] AS RAISERROR(''<><><> GotHere <><><>'',16,10);'); - BEGIN TRY - EXEC tSQLt.RunTestClass MyTestClass; - END TRY - BEGIN CATCH - SET @ErrorRaised = 1; - END CATCH - IF(@ErrorRaised = 0) - BEGIN - EXEC tSQLt.Fail 'tSQLt.RunTestClass did not raise an error!'; - END -END; -GO + BEGIN TRY + EXEC tSQLt.Run 'MyTestClass.Test Case A'; + END TRY + BEGIN CATCH + --This space left intentionally blank + END CATCH + SELECT Class, TestCase, Msg + INTO #Actual + FROM tSQLt.TestResult; -CREATE PROC Run_Methods_Tests.test_Run_handles_test_names_with_spaces -AS -BEGIN - DECLARE @ErrorRaised INT; SET @ErrorRaised = 0; + SELECT TOP(0) A.Class,A.TestCase,A.Msg AS [%Msg] INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + SELECT 'MyTestClass' Class, 'Test Case A' TestCase, '%<><><> GotHere <><><>%' Msg - EXEC('CREATE SCHEMA MyTestClass;'); - EXEC('CREATE PROC MyTestClass.[Test Case A] AS RAISERROR(''GotHere'',16,10);'); + SELECT * INTO #Compare + FROM( + SELECT '>' _R_,* FROM #Actual AS A WHERE NOT EXISTS(SELECT 1 FROM #Expected E WHERE A.Class = E.Class AND A.TestCase = E.TestCase AND A.Msg LIKE E.[%Msg]) + UNION ALL + SELECT '<' _R_,* FROM #Expected AS E WHERE NOT EXISTS(SELECT 1 FROM #Actual A WHERE A.Class = E.Class AND A.TestCase = E.TestCase AND A.Msg LIKE E.[%Msg]) + )X - BEGIN TRY - EXEC tSQLt.Run 'MyTestClass.Test Case A'; - END TRY - BEGIN CATCH - SET @ErrorRaised = 1; - END CATCH - SELECT Class, TestCase, Msg - INTO actual - FROM tSQLt.TestResult; - SELECT 'MyTestClass' Class, 'Test Case A' TestCase, 'GotHere[16,10]{Test Case A,1}' Msg - INTO expected; - - EXEC tSQLt.AssertEqualsTable 'expected', 'actual'; + EXEC tSQLt.AssertEmptyTable '#Compare'; END; GO @@ -242,13 +274,13 @@ BEGIN EXEC tSQLt.RunTestClass MyTestClass; SELECT Class, TestCase - INTO actual + INTO #Actual FROM tSQLt.TestResult; SELECT 'MyTestClass' Class, 'Test Case A' TestCase - INTO expected; + INTO #Expected; - EXEC tSQLt.AssertEqualsTable 'expected', 'actual'; + EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; END; GO @@ -286,7 +318,7 @@ AS BEGIN EXEC tSQLt.SpyProcedure 'tSQLt.Private_OutputTestResults'; - EXEC tSQLt.Private_Run 'NoTestSchema.NoTest','SomeTestResultFormatter'; + EXEC tSQLt.Private_Run @TestName = 'NoTestSchema.NoTest', @TestResultFormatter = 'SomeTestResultFormatter'; SELECT TestResultFormatter INTO #Actual @@ -379,16 +411,16 @@ BEGIN EXEC tSQLt.CaptureOutput 'EXEC tSQLt.NullTestResultFormatter'; SELECT OutputText - INTO #actual + INTO #Actual FROM tSQLt.CaptureOutputLog; SELECT TOP(0) * - INTO #expected - FROM #actual; + INTO #Expected + FROM #Actual; - INSERT INTO #expected(OutputText)VALUES(NULL); + INSERT INTO #Expected(OutputText)VALUES(NULL); - EXEC tSQLt.AssertEqualsTable '#expected','#actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO @@ -526,15 +558,15 @@ BEGIN SELECT @XML = CAST(Message AS XML) FROM tSQLt.Private_PrintXML_SpyProcedureLog; SELECT TestCase.value('@name','NVARCHAR(MAX)') AS TestCase, TestCase.value('failure[1]/@message','NVARCHAR(MAX)') AS Msg - INTO #actual + INTO #Actual FROM @XML.nodes('/testsuites/testsuite/testcase') X(TestCase); SELECT TestCase,Msg - INTO #expected + INTO #Expected FROM tSQLt.TestResult; - EXEC tSQLt.AssertEqualsTable '#expected','#actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO @@ -566,14 +598,14 @@ BEGIN TestCase.value('@tests','NVARCHAR(MAX)') AS tests, TestCase.value('@failures','NVARCHAR(MAX)') AS failures, TestCase.value('@errors','NVARCHAR(MAX)') AS errors - INTO #actual + INTO #Actual FROM @XML.nodes('/testsuites/testsuite') X(TestCase); SELECT N'MyTestClass' AS Class, 4 tests, 2 failures, 1 errors - INTO #expected + INTO #Expected - EXEC tSQLt.AssertEqualsTable '#expected','#actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO @@ -607,19 +639,19 @@ BEGIN TestCase.value('@tests','NVARCHAR(MAX)') AS tests, TestCase.value('@failures','NVARCHAR(MAX)') AS failures, TestCase.value('@errors','NVARCHAR(MAX)') AS errors - INTO #actual + INTO #Actual FROM @XML.nodes('/testsuites/testsuite') X(TestCase); SELECT * - INTO #expected + INTO #Expected FROM ( SELECT N'MyTestClass1' AS Class, 2 tests, 1 failures, 0 errors UNION ALL SELECT N'MyTestClass2' AS Class, 3 tests, 2 failures, 1 errors ) AS x; - EXEC tSQLt.AssertEqualsTable '#expected','#actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO @@ -649,15 +681,15 @@ BEGIN SELECT TestCase.value('../@name','NVARCHAR(MAX)') AS Class, TestCase.value('@name','NVARCHAR(MAX)') AS TestCase - INTO #actual + INTO #Actual FROM @XML.nodes('/testsuites/testsuite/testcase') X(TestCase); SELECT Class,TestCase - INTO #expected + INTO #Expected FROM tSQLt.TestResult; - EXEC tSQLt.AssertEqualsTable '#expected','#actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO CREATE PROC Run_Methods_Tests.[test XmlResultFormatter includes duration for each test] @@ -677,7 +709,7 @@ BEGIN INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime) VALUES ('MyTestClass2', 'testC', 'Failure', '2015-07-24T00:00:01.111', '2015-08-17T20:31:24.758'); INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime) - VALUES ('MyTestClass2', 'testD', 'Error', '2015-07-24T00:00:01.666', '2015-07-24T00:00:01.669'); + VALUES ('MyTestClass2', 'testD', 'Error', '2015-07-24T00:00:01.664', '2015-07-24T00:00:01.667'); EXEC tSQLt.XmlResultFormatter; @@ -687,7 +719,7 @@ BEGIN TestCase.value('../@name','NVARCHAR(MAX)') AS Class, TestCase.value('@name','NVARCHAR(MAX)') AS TestCase, TestCase.value('@time','NVARCHAR(MAX)') AS Time - INTO #actual + INTO #Actual FROM @XML.nodes('/testsuites/testsuite/testcase') X(TestCase); @@ -696,15 +728,15 @@ BEGIN FROM #Actual; INSERT INTO #Expected - VALUES('MyTestClass1', 'testA', '0.136'); + VALUES('MyTestClass1', 'testA', '0.138'); INSERT INTO #Expected VALUES('MyTestClass1', 'testB', '1.633'); INSERT INTO #Expected - VALUES('MyTestClass2', 'testC', '2147483.646'); + VALUES('MyTestClass2', 'testC', '2147483.647'); INSERT INTO #Expected VALUES('MyTestClass2', 'testD', '0.003'); - EXEC tSQLt.AssertEqualsTable '#expected','#actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO CREATE PROC Run_Methods_Tests.[test XmlResultFormatter includes start time and total duration per class] @@ -734,7 +766,7 @@ BEGIN TestCase.value('@name','NVARCHAR(MAX)') AS TestCase, TestCase.value('@timestamp','NVARCHAR(MAX)') AS Timestamp, TestCase.value('@time','NVARCHAR(MAX)') AS Time - INTO #actual + INTO #Actual FROM @XML.nodes('/testsuites/testsuite') X(TestCase); @@ -745,9 +777,9 @@ BEGIN INSERT INTO #Expected VALUES('MyTestClass1', '2015-07-24T00:00:01', '1.633'); INSERT INTO #Expected - VALUES('MyTestClass2', '2015-07-24T00:00:00', '73884.090'); + VALUES('MyTestClass2', '2015-07-24T00:00:00', '73884.091'); - EXEC tSQLt.AssertEqualsTable '#expected','#actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO CREATE PROC Run_Methods_Tests.[test XmlResultFormatter includes other required fields] @@ -780,7 +812,7 @@ BEGIN TestCase.value('@name','NVARCHAR(MAX)') AS Testname, TestCase.value('failure[1]/@type','NVARCHAR(MAX)') AS FailureType, TestCase.value('error[1]/@type','NVARCHAR(MAX)') AS ErrorType - INTO #actual + INTO #Actual FROM @XML.nodes('/testsuites/testsuite/testcase') X(TestCase); @@ -798,7 +830,7 @@ BEGIN INSERT INTO #Expected VALUES(@ServerName,2,'tSQLt','testD',NULL,'SQL Error'); - EXEC tSQLt.AssertEqualsTable '#expected','#actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO CREATE PROCEDURE Run_Methods_Tests.[test RunWithNullResults calls Run with NullTestResultFormatter] @@ -1328,144 +1360,92 @@ BEGIN EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; END; GO -CREATE PROC Run_Methods_Tests.[test that tSQLt.Private_Run captures start time] +CREATE PROC Run_Methods_Tests.[ptest that tSQLt.Private_Run correctly captures] + @the NVARCHAR(MAX), + @whenTest NVARCHAR(MAX) AS BEGIN EXEC('EXEC tSQLt.DropClass innertest;'); EXEC('CREATE SCHEMA innertest;'); - EXEC('CREATE PROC innertest.testMe as WAITFOR DELAY ''00:00:00.111'';'); - EXEC tSQLt.CaptureOutput @command='EXEC tSQLt.Private_Run ''innertest.testMe'', ''tSQLt.NullTestResultFormatter'';'; + IF(@whenTest = 'succeeds') + BEGIN + EXEC('CREATE PROC innertest.testMe as EXEC tSQLt_testutil.WaitForMS 120;'); + END; + IF(@whenTest = 'fails') + BEGIN + EXEC('CREATE PROC innertest.testMe as EXEC tSQLt_testutil.WaitForMS 120;EXEC tSQLt.Fail ''XX'';'); + END; + + --Prime cache to prevent unintentional "padding" of the execution time during compilation + EXEC tSQLt.CaptureOutput @command='EXEC tSQLt.Private_Run ''innertest.testMe'', ''tSQLt.NullTestResultFormatter'';'; + + DECLARE @actual DATETIME2; + DECLARE @after DATETIME2; + DECLARE @before DATETIME2; - DECLARE @actual DATETIME; - DECLARE @after DATETIME; - DECLARE @before DATETIME; + DELETE FROM tSQLt.TestResult; - SET @before = GETDATE(); + SET @before = SYSDATETIME(); EXEC tSQLt.CaptureOutput @command='EXEC tSQLt.Private_Run ''innertest.testMe'', ''tSQLt.NullTestResultFormatter'';'; - SET @after = GETDATE(); - - SELECT @actual = TestStartTime - FROM tSQLt.TestResult AS TR - + SET @after = SYSDATETIME(); + + DECLARE @expectedIntervalStart DATETIME2; + DECLARE @expectedIntervalEnd DATETIME2; + + IF(@the = 'StartTime') + BEGIN + SELECT + @actual = (SELECT TestStartTime FROM tSQLt.TestResult), + @expectedIntervalStart = @before, + @expectedIntervalEnd = DATEADD(MILLISECOND, -120, @after); + END; + IF(@the = 'EndTime') + BEGIN + SELECT + @actual = (SELECT TestEndTime FROM tSQLt.TestResult), + @expectedIntervalStart = DATEADD(MILLISECOND,120,@before), + @expectedIntervalEnd = @after; + END; + DECLARE @msg NVARCHAR(MAX); - IF(@actual < DATEADD(MILLISECOND,-9,@before) OR @actual > DATEADD(MILLISECOND,-102,@after) OR @actual IS NULL) + IF(@actual < @expectedIntervalStart OR @actual > @expectedIntervalEnd OR @actual IS NULL) BEGIN SET @msg = 'Expected:'+ - CONVERT(NVARCHAR(MAX),DATEADD(MILLISECOND,-9,@before),121)+ + CONVERT(NVARCHAR(MAX),@expectedIntervalStart,121)+ ' <= '+ ISNULL(CONVERT(NVARCHAR(MAX),@actual,121),'!NULL!')+ ' <= '+ - CONVERT(NVARCHAR(MAX),DATEADD(MILLISECOND,-102,@after),121); + CONVERT(NVARCHAR(MAX),@expectedIntervalEnd,121); EXEC tSQLt.Fail @msg; END; END; GO -CREATE PROC Run_Methods_Tests.[test that tSQLt.Private_Run captures finish time] +CREATE PROC Run_Methods_Tests.[test that tSQLt.Private_Run captures start time] AS BEGIN - EXEC('EXEC tSQLt.DropClass innertest;'); - EXEC('CREATE SCHEMA innertest;'); - EXEC('CREATE PROC innertest.testMe as WAITFOR DELAY ''00:00:00.111'';'); - - EXEC tSQLt.CaptureOutput @command='EXEC tSQLt.Private_Run ''innertest.testMe'', ''tSQLt.NullTestResultFormatter'';'; - - DECLARE @actual DATETIME; - DECLARE @after DATETIME; - DECLARE @before DATETIME; - - SET @before = GETDATE(); - - EXEC tSQLt.CaptureOutput @command='EXEC tSQLt.Private_Run ''innertest.testMe'', ''tSQLt.NullTestResultFormatter'';'; - - SET @after = GETDATE(); - - SELECT @actual = TestEndTime - FROM tSQLt.TestResult AS TR - - DECLARE @msg NVARCHAR(MAX); - IF(@actual < DATEADD(MILLISECOND,102,@before) OR @actual > DATEADD(MILLISECOND,9,@after) OR @actual IS NULL) - BEGIN - SET @msg = - 'Expected:'+ - CONVERT(NVARCHAR(MAX),DATEADD(MILLISECOND,102,@before),121)+ - ' <= '+ - ISNULL(CONVERT(NVARCHAR(MAX),@actual,121),'!NULL!')+ - ' <= '+ - CONVERT(NVARCHAR(MAX),DATEADD(MILLISECOND,9,@after),121); - EXEC tSQLt.Fail @msg; - END; + EXEC Run_Methods_Tests.[ptest that tSQLt.Private_Run correctly captures] @the = 'StartTime', @whenTest = 'succeeds'; END; GO -CREATE PROC Run_Methods_Tests.[test that tSQLt.Private_Run captures finish time for failing test] +CREATE PROC Run_Methods_Tests.[test that tSQLt.Private_Run captures start time for failing test] AS BEGIN - EXEC('EXEC tSQLt.DropClass innertest;'); - EXEC('CREATE SCHEMA innertest;'); - EXEC('CREATE PROC innertest.testMe as WAITFOR DELAY ''00:00:00.111'';EXEC tSQLt.Fail ''XX'';'); - - EXEC tSQLt.CaptureOutput @command='EXEC tSQLt.Private_Run ''innertest.testMe'', ''tSQLt.NullTestResultFormatter'';'; - - DECLARE @actual DATETIME; - DECLARE @after DATETIME; - DECLARE @before DATETIME; - - SET @before = GETDATE(); - - EXEC tSQLt.CaptureOutput @command='EXEC tSQLt.Private_Run ''innertest.testMe'', ''tSQLt.NullTestResultFormatter'';'; - - SET @after = GETDATE(); - - SELECT @actual = TestEndTime - FROM tSQLt.TestResult AS TR - - DECLARE @msg NVARCHAR(MAX); - IF(@actual < DATEADD(MILLISECOND,111,@before) OR @actual > @after OR @actual IS NULL) - BEGIN - SET @msg = - 'Expected:'+ - CONVERT(NVARCHAR(MAX),@before,121)+ - ' <= '+ - ISNULL(CONVERT(NVARCHAR(MAX),@actual,121),'!NULL!')+ - ' <= '+ - CONVERT(NVARCHAR(MAX),DATEADD(MILLISECOND,-111,@after),121); - EXEC tSQLt.Fail @msg; - END; + EXEC Run_Methods_Tests.[ptest that tSQLt.Private_Run correctly captures] @the = 'StartTime', @whenTest = 'fails'; END; GO -CREATE PROC Run_Methods_Tests.[test DefaultResultFormatter outputs test execution duration] +CREATE PROC Run_Methods_Tests.[test that tSQLt.Private_Run captures finish time] AS BEGIN - EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; - DECLARE @ttt_object_id INT;SET @ttt_object_id = OBJECT_ID('tSQLt.TableToText'); - EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.TableToText', @CommandToExecute = 'EXEC(''SELECT * INTO Run_Methods_Tests.Actual FROM ''+@TableName);'; - - EXEC(' - INSERT INTO tSQLt.TestResult(Name,Result,TestStartTime,TestEndTime) - VALUES(''[a test class].[test 1]'',''success'',''2015-07-18T00:00:01.000'',''2015-07-18T00:10:10.555''); - INSERT INTO tSQLt.TestResult(Name,Result,TestStartTime,TestEndTime) - VALUES(''[a test class].[test 2]'',''failure'',''2015-07-18T00:00:02.000'',''2015-07-18T00:22:03.444''); - '); - - EXEC tSQLt.DefaultResultFormatter; - - DROP PROCEDURE tSQLt.TableToText; - DECLARE @new_ttt_name NVARCHAR(MAX); SET @new_ttt_name = 'tSQLt.'+QUOTENAME(OBJECT_NAME(@ttt_object_id)); - EXEC sys.sp_rename @new_ttt_name,'TableToText','OBJECT'; - - SELECT TOP(0) * - INTO Run_Methods_Tests.Expected - FROM Run_Methods_Tests.Actual; - - INSERT INTO Run_Methods_Tests.Expected - VALUES(1,'[a test class].[test 1]',' 609556','success'); - INSERT INTO Run_Methods_Tests.Expected - VALUES(2,'[a test class].[test 2]','1321443','failure'); - - EXEC tSQLt.AssertEqualsTable 'Run_Methods_Tests.Expected','Run_Methods_Tests.Actual'; + EXEC Run_Methods_Tests.[ptest that tSQLt.Private_Run correctly captures] @the = 'EndTime', @whenTest = 'succeeds'; +END; +GO +CREATE PROC Run_Methods_Tests.[test that tSQLt.Private_Run captures finish time for failing test] +AS +BEGIN + EXEC Run_Methods_Tests.[ptest that tSQLt.Private_Run correctly captures] @the = 'EndTime', @whenTest = 'fails'; END; GO CREATE PROC Run_Methods_Tests.[test Privat_GetCursorForRunNew returns all test classes created after(!) tSQLt.Reset was called] @@ -1478,29 +1458,14 @@ BEGIN EXEC tSQLt.NewTestClass 'Test Class B'; EXEC tSQLt.NewTestClass 'Test Class C'; - DECLARE @TestClassCursor CURSOR; - EXEC tSQLt.Private_GetCursorForRunNew @TestClassCursor = @TestClassCursor OUT; - - SELECT Class - INTO #Actual - FROM tSQLt.TestResult - WHERE 1=0; - - DECLARE @TestClass NVARCHAR(MAX); - WHILE(1=1) - BEGIN - FETCH NEXT FROM @TestClassCursor INTO @TestClass; - IF(@@FETCH_STATUS<>0)BREAK; - INSERT INTO #Actual VALUES(@TestClass); - END; - CLOSE @TestClassCursor; - DEALLOCATE @TestClassCursor; + CREATE TABLE #TestClassesForRunCursor(Name NVARCHAR(MAX)); + EXEC tSQLt.Private_GetCursorForRunNew; - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + SELECT TOP(0) A.* INTO #Expected FROM #TestClassesForRunCursor A RIGHT JOIN #TestClassesForRunCursor X ON 1=0; INSERT INTO #Expected VALUES('Test Class B'); INSERT INTO #Expected VALUES('Test Class C'); - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#TestClassesForRunCursor'; END; GO @@ -1514,29 +1479,13 @@ BEGIN EXEC tSQLt.NewTestClass 'Test Class C'; EXEC tSQLt.DropClass 'Test Class C'; - DECLARE @TestClassCursor CURSOR; - EXEC tSQLt.Private_GetCursorForRunNew @TestClassCursor = @TestClassCursor OUT; - - SELECT Class - INTO #Actual - FROM tSQLt.TestResult - WHERE 1=0; - - DECLARE @TestClass NVARCHAR(MAX); - WHILE(1=1) - BEGIN - FETCH NEXT FROM @TestClassCursor INTO @TestClass; - IF(@@FETCH_STATUS<>0)BREAK; - INSERT INTO #Actual VALUES(@TestClass); - END; - CLOSE @TestClassCursor; - DEALLOCATE @TestClassCursor; + CREATE TABLE #TestClassesForRunCursor(Name NVARCHAR(MAX)); + EXEC tSQLt.Private_GetCursorForRunNew; - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + SELECT TOP(0) A.* INTO #Expected FROM #TestClassesForRunCursor A RIGHT JOIN #TestClassesForRunCursor X ON 1=0; INSERT INTO #Expected VALUES('Test Class B'); - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; - + EXEC tSQLt.AssertEqualsTable '#Expected','#TestClassesForRunCursor'; END; GO CREATE PROC Run_Methods_Tests.[test Privat_RunNew calls Private_RunCursor with correct cursor] @@ -1703,3 +1652,852 @@ BEGIN EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO +CREATE PROC Run_Methods_Tests.[test PrepareTestResultForOutput calculates test duration] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + + EXEC(' + INSERT INTO tSQLt.TestResult(Name,TestStartTime,TestEndTime) + VALUES(''[a test class].[test 1]'',''2015-07-18T00:00:01.000'',''2015-07-18T00:10:10.555''); + INSERT INTO tSQLt.TestResult(Name,TestStartTime,TestEndTime) + VALUES(''[a test class].[test 2]'',''2015-07-18T00:00:02.000'',''2015-07-18T00:22:03.444''); + '); + + SELECT PTRFO.[Test Case Name], + PTRFO.[Dur(ms)] + INTO #Actual + FROM tSQLt.Private_PrepareTestResultForOutput() AS PTRFO + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[a test class].[test 1]',' 609555'), + ('[a test class].[test 2]','1321444'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROC Run_Methods_Tests.[test PrepareTestResultForOutput orders tests appropriately] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + EXEC(' + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 4]'',''Failure''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 1]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 7]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 3]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 9]'',''Failure''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 5]'',''Skipped''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 6]'',''Error''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 2]'',''Error''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 8]'',''Skipped''); + '); + + SELECT + PTRFO.No, + PTRFO.[Test Case Name], + PTRFO.Result + INTO #Actual + FROM tSQLt.Private_PrepareTestResultForOutput() AS PTRFO + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(1,'[a test class].[test 1]','Success'), + (2,'[a test class].[test 3]','Success'), + (3,'[a test class].[test 7]','Success'), + (4,'[a test class].[test 5]','Skipped'), + (5,'[a test class].[test 8]','Skipped'), + (6,'[a test class].[test 4]','Failure'), + (7,'[a test class].[test 9]','Failure'), + (8,'[a test class].[test 2]','Error'), + (9,'[a test class].[test 6]','Error'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE FUNCTION Run_Methods_Tests.[Returns 7,SomeSpecificName,42,Failure]() +RETURNS TABLE +AS +RETURN + SELECT 7 No,'SomeSpecificName' [Test Case Name],42 [Dur(ms)], 'Failure' Result; +GO +CREATE PROC Run_Methods_Tests.[test DefaultResultFormatter is using PrepareTestResultForOutput] +AS +BEGIN + EXEC tSQLt.FakeFunction + @FunctionName = 'tSQLt.Private_PrepareTestResultForOutput', + @FakeFunctionName = 'Run_Methods_Tests.[Returns 7,SomeSpecificName,42,Failure]'; + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; + + EXEC tSQLt.DefaultResultFormatter; + + EXEC tSQLt_testutil.AssertTableContainsString + @Table = 'tSQLt.Private_Print_SpyProcedureLog', + @Column = 'Message', + @Pattern = '%7%SomeSpecificName%42%Failure%'; + +END; +GO +CREATE PROC Run_Methods_Tests.[test XmlResultFormatter creates testsuite with multiple test elements some skipped] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + + EXEC tSQLt.SpyProcedure 'tSQLt.Private_PrintXML'; + + DECLARE @XML XML; + + DELETE FROM tSQLt.TestResult; + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result, Msg) + VALUES ('MyTestClass', 'testA', 'XYZ', 'Skipped', 'testA intentionally skipped'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result, Msg) + VALUES ('MyTestClass', 'testB', 'XYZ', 'Success', NULL); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result, Msg) + VALUES ('MyTestClass', 'testC', 'XYZ', 'Skipped', 'testC intentionally skipped'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result, Msg) + VALUES ('MyTestClass', 'testD', 'XYZ', 'Success', NULL); + + EXEC tSQLt.XmlResultFormatter; + + SELECT @XML = CAST(Message AS XML) FROM tSQLt.Private_PrintXML_SpyProcedureLog; + + SELECT TestCase.value('@name','NVARCHAR(MAX)') AS TestCase, TestCase.value('skipped[1]/@message','NVARCHAR(MAX)') AS Msg + INTO #Actual + FROM @XML.nodes('/testsuites/testsuite/testcase') X(TestCase); + + + SELECT TestCase,Msg + INTO #Expected + FROM tSQLt.TestResult; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROC Run_Methods_Tests.[test XmlResultFormatter sets correct counts for skipped tests] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + + EXEC tSQLt.SpyProcedure 'tSQLt.Private_PrintXML'; + + DECLARE @XML XML; + + DELETE FROM tSQLt.TestResult; + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result) + VALUES ('MyTestClass1', 'testA', 'XYZ', 'Failure'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result) + VALUES ('MyTestClass1', 'testB', 'XYZ', 'Success'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result) + VALUES ('MyTestClass2', 'testC', 'XYZ', 'Skipped'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result) + VALUES ('MyTestClass2', 'testD', 'XYZ', 'Error'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result) + VALUES ('MyTestClass2', 'testE', 'XYZ', 'Failure'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result) + VALUES ('MyTestClass3', 'testF', 'XYZ', 'Skipped'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result) + VALUES ('MyTestClass3', 'testG', 'XYZ', 'Error'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result) + VALUES ('MyTestClass3', 'testH', 'XYZ', 'Skipped'); + INSERT INTO tSQLt.TestResult (Class, TestCase, TranName, Result) + VALUES ('MyTestClass3', 'testI', 'XYZ', 'Skipped'); + + EXEC tSQLt.XmlResultFormatter; + + SELECT @XML = CAST(Message AS XML) FROM tSQLt.Private_PrintXML_SpyProcedureLog; + + SELECT + TestCase.value('@name','NVARCHAR(MAX)') AS Class, + TestCase.value('@tests','NVARCHAR(MAX)') AS Tests, + TestCase.value('@skipped','NVARCHAR(MAX)') AS Skipped + INTO #Actual + FROM @XML.nodes('/testsuites/testsuite') X(TestCase); + +-- SELECT * FROM tSQLt.Private_PrintXML_SpyProcedureLog; + + SELECT * + INTO #Expected + FROM ( + SELECT N'MyTestClass1' AS Class, 2 Tests, 0 Skipped + UNION ALL + SELECT N'MyTestClass2' AS Class, 3 Tests, 1 Skipped + UNION ALL + SELECT N'MyTestClass3' AS Class, 4 Tests, 3 Skipped + ) AS x; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Run_Methods_Tests.SetupXMLSchemaCollection +AS +BEGIN +--Valid JUnit XML Schema +--Source:https://raw.githubusercontent.com/windyroad/JUnit-Schema/master/JUnit.xsd +--Source with commit-id: https://raw.githubusercontent.com/windyroad/JUnit-Schema/d6daa414c448da22b810c8562f9d6fca086983ba/JUnit.xsd +DECLARE @cmd NVARCHAR(MAX);SET @cmd = +' + + + JUnit test result schema for the Apache Ant JUnit and JUnitReport tasks +Copyright οΏ½ 2011, Windy Road Technology Pty. Limited +The Apache Ant JUnit XML Schema is distributed under the terms of the Apache License Version 2.0 http://www.apache.org/licenses/ +Permission to waive conditions of this license may be requested from Windy Road Support (http://windyroad.org/support). + + + + + + + + + + Contains an aggregation of testsuite results + + + + + + + + + + Derived from testsuite/@name in the non-aggregated documents + + + + + Starts at ''0'' for the first testsuite and is incremented by 1 for each following testsuite + + + + + + + + + + + + Contains the results of exexuting a testsuite + + + + + Properties (e.g., environment settings) set during test execution + + + + + + + + + + + + + + + + + + + + + + + + + Indicates that the test errored. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test. Contains as a text node relevant data for the error, e.g., a stack trace + + + + + + + The error message. e.g., if a java exception is thrown, the return value of getMessage() + + + + + The type of error that occured. e.g., if a java execption is thrown the full class name of the exception. + + + + + + + + + Indicates that the test failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals. Contains as a text node relevant data for the failure, e.g., a stack trace + + + + + + + The message specified in the assert + + + + + The type of the assert. + + + + + + + + + + Name of the test method + + + + + Full class name for the class the test method is in. + + + + + Time taken (in seconds) to execute the test + + + + + + + Data that was written to standard out while the test was executed + + + + + + + + + + Data that was written to standard error while the test was executed + + + + + + + + + + + Full class name of the test for non-aggregated testsuite documents. Class name without the package for aggregated testsuites documents + + + + + + + + + + when the test was executed. Timezone may not be specified. + + + + + Host on which the tests were executed. ''localhost'' should be used if the hostname cannot be determined. + + + + + + + + + + The total number of tests in the suite + + + + + The total number of tests in the suite that failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals + + + + + The total number of tests in the suite that errored. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test. + + + + + The total number of ignored or skipped tests in the suite. + + + + + Time taken (in seconds) to execute the tests in the suite + + + + + + + + +'; +SET @cmd = 'CREATE XML SCHEMA COLLECTION Run_Methods_Tests.ValidJUnitXML AS '''+REPLACE(REPLACE(@cmd,'''',''''''),'οΏ½','(c)')+''';'; +--EXEC(@cmd); +EXEC tSQLt.CaptureOutput @cmd; +END; +GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROC Run_Methods_Tests.[test XmlResultFormatter returns XML that validates against the JUnit test result xsd] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + EXEC tSQLt.SpyProcedure 'tSQLt.Private_PrintXML'; + + DELETE FROM tSQLt.TestResult; + INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime,Msg) + VALUES ('MyTestClass1', 'testA', 'Failure', '2015-07-24T00:00:01.000', '2015-07-24T00:00:01.138','failed intentionally'); + INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime,Msg) + VALUES ('MyTestClass1', 'testB', 'Success', '2015-07-24T00:00:02.000', '2015-07-24T00:00:02.633',NULL); + INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime,Msg) + VALUES ('MyTestClass2', 'testC', 'Failure', '2015-07-24T00:00:01.111', '2015-07-24T20:31:24.758','failed intentionally'); + INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime,Msg) + VALUES ('MyTestClass2', 'testD', 'Error', '2015-07-24T00:00:00.667', '2015-07-24T00:00:01.055','errored intentionally'); + INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime,Msg) + VALUES ('MyTestClass2', 'testE', 'Skipped', '2015-07-24T00:00:01.111', '2015-07-24T20:31:24.758','skipped intentionally'); + INSERT INTO tSQLt.TestResult (Class, TestCase, Result, TestStartTime, TestEndTime,Msg) + VALUES ('MyTestClass2', 'testF', 'Skipped', '2015-07-24T00:00:00.667', '2015-07-24T00:00:01.055','skipped intentionally'); + + EXEC tSQLt.XmlResultFormatter; + + EXEC tSQLt.ExpectNoException; +RETURN + EXEC Run_Methods_Tests.SetupXMLSchemaCollection; + DECLARE @TestResultXML XML; + SELECT @TestResultXML = CAST(Message AS XML) FROM tSQLt.Private_PrintXML_SpyProcedureLog; + -- The assignment below will fail if the @TestResultXML is not schema conform + EXEC sp_executesql N'DECLARE @TestSchema XML(Run_Methods_Tests.ValidJUnitXML) = @TestResultXML',N'@TestResultXML XML',@TestResultXML; +END; +GO +--[@tSQLt:MinSqlMajorVersion](10) +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.Private_InputBuffer does not produce output] +AS +BEGIN + DECLARE @Actual NVARCHAR(MAX);SET @Actual = ''; + + EXEC tSQLt.CaptureOutput 'DECLARE @r NVARCHAR(MAX);EXEC tSQLt.Private_InputBuffer @r OUT;'; + + SELECT @Actual = COL.OutputText FROM tSQLt.CaptureOutputLog AS COL; + + EXEC tSQLt.AssertEqualsString @Expected = NULL, @Actual = @Actual; +END; +GO +CREATE PROCEDURE Run_Methods_Tests.[test the Test Case Summary line is "printed" as error if there is one error result] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + EXEC(' + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 1]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 2]'',''Error''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 3]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 4]'',''Skipped''); + '); + + EXEC tSQLt.SetSummaryError @SummaryError = 1; --Global setting to actually raise an error -- the build is turning this off + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'Test Case Summary:%'; + EXEC tSQLt.DefaultResultFormatter; + +END +GO +CREATE PROCEDURE Run_Methods_Tests.[test the Test Case Summary line is "printed" as error if there is one failure result] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + EXEC(' + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 1]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 2]'',''Failure''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 3]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 4]'',''Skipped''); + '); + + EXEC tSQLt.SetSummaryError @SummaryError = 1; --Global setting to actually raise an error -- the build is turning this off + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'Test Case Summary:%'; + EXEC tSQLt.DefaultResultFormatter; + +END +GO +CREATE PROCEDURE Run_Methods_Tests.[test the Test Case Summary line is "printed" as error if there are multiple failure and error results] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + EXEC(' + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 1]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 2]'',''Failure''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 3]'',''Error''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 4]'',''Failure''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 5]'',''Skipped''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 6]'',''Error''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 7]'',''Failure''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 8]'',''Success''); + '); + + EXEC tSQLt.SetSummaryError @SummaryError = 1; --Global setting to actually raise an error -- the build is turning this off + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'Test Case Summary:%'; + EXEC tSQLt.DefaultResultFormatter; + +END +GO +CREATE PROCEDURE Run_Methods_Tests.[test the Test Case Summary line is not "printed" as error if there are only success results] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + EXEC(' + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 1]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 2]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 3]'',''Success''); + '); + + EXEC tSQLt.SetSummaryError @SummaryError = 1; --Global setting to actually raise an error -- the build is turning this off + + EXEC tSQLt.ExpectNoException; + EXEC tSQLt.DefaultResultFormatter; + +END +GO +CREATE PROCEDURE Run_Methods_Tests.[test the Test Case Summary line is not "printed" as error if there are only skipped results] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + EXEC(' + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 1]'',''Skipped''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 2]'',''Skipped''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 3]'',''Skipped''); + '); + + EXEC tSQLt.SetSummaryError @SummaryError = 1; --Global setting to actually raise an error -- the build is turning this off + + EXEC tSQLt.ExpectNoException; + EXEC tSQLt.DefaultResultFormatter; + +END +GO +CREATE PROCEDURE Run_Methods_Tests.[test the Test Case Summary line is not "printed" as error if there are only success and skipped results] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestResult'; + EXEC(' + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 1]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 2]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 3]'',''Success''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 4]'',''Skipped''); + INSERT INTO tSQLt.TestResult(Name,Result)VALUES(''[a test class].[test 5]'',''Skipped''); + '); + + EXEC tSQLt.SetSummaryError @SummaryError = 1; --Global setting to actually raise an error -- the build is turning this off + + EXEC tSQLt.ExpectNoException; + EXEC tSQLt.DefaultResultFormatter; + +END +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.Private_InputBuffer returns non-empty string] +AS +BEGIN + DECLARE @r NVARCHAR(MAX);EXEC tSQLt.Private_InputBuffer @r OUT; + IF(ISNULL(@r,'') = '') + BEGIN + EXEC tSQLt.Fail 'tSQLt.Private_InputBuffer returned NULL or an empty string.'; + END; +END +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.RunAll can handle classes with single quotes] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RunTestClass'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestClasses'; + EXEC('INSERT INTO tSQLt.TestClasses VALUES(''a class with a '''' in the middle'',12321);'); + + EXEC tSQLt.RunAll; + + SELECT TestClassName + INTO #Actual + FROM tSQLt.Private_RunTestClass_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES ('a class with a '' in the middle'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.RunAll executes tests with single quotes in class and name] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestClasses'; + EXEC('INSERT INTO tSQLt.TestClasses VALUES(''a class with a '''' in the middle'',12321);'); + + EXEC tSQLt.SetSummaryError 0; + + EXEC tSQLt.RunAll; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.RunTestClass executes tests with single quotes in class and name] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + + EXEC tSQLt.RunTestClass @TestClassName = '[a class with a '' in the middle]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.Run executes test class with single quotes in name] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + + EXEC tSQLt.Run '[a class with a '' in the middle]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.Run executes test with single quotes in class and test names] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + + EXEC tSQLt.Run '[a class with a '' in the middle].[test with a '' in the middle]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.RunNew executes test class with single quotes in class and test names] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestClasses'; + + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NewTestClassList'; + + EXEC('INSERT INTO tSQLt.TestClasses(Name) VALUES(''a class with a '''' in the middle'');'); + EXEC('INSERT INTO tSQLt.Private_NewTestClassList(ClassName) VALUES(''a class with a '''' in the middle'');'); + + EXEC tSQLt.SetSummaryError 0; + + EXEC tSQLt.RunNew; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test FATAL error prevents subsequent tSQLt.Run% calls] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'IF(@Action = ''Reset'')BEGIN RAISERROR(''Some Fatal Error'',16,10);END;'; + + BEGIN TRY + EXEC tSQLt.Run 'MyInnerTests', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + END TRY + BEGIN CATCH + /* not interested in this error */ + END CATCH; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt is in an invalid state. Please reinstall tSQLt.'; + EXEC tSQLt.Run 'MyInnerTests', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test when @Result=FATAL an appropriate error message is raised] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'IF(@Action = ''Reset'')BEGIN RAISERROR(''Some Fatal Error'',16,10);END;'; + + EXEC tSQLt.SetSummaryError @SummaryError = 1; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'The last test has invalidated the current installation of tSQLt. Please reinstall tSQLt.'; + EXEC tSQLt.Run 'MyInnerTests'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test when @Result=Abort an appropriate error message is raised] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles', @CommandToExecute = 'RAISERROR(''Some Fatal Error'',16,10);'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + + EXEC tSQLt.SetSummaryError @SummaryError = 1; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Aborting the current execution of tSQLt due to a severe error.'; + EXEC tSQLt.Run 'MyInnerTests'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +--[@tSQLt:SkipTest]('TODO: tSQLt should handle this, but does not at the moment because of the issue with transactions started within a try-catch block') +CREATE PROCEDURE Run_Methods_Tests.[test produces meaningful error when pre and post transactions counts don't match] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTestsA' + EXEC('CREATE PROCEDURE MyInnerTestsA.[test should execute outside of transaction] AS BEGIN TRAN;'); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'SOMETHING RATHER', @ExpectedSeverity = NULL, @ExpectedState = NULL; + EXEC tSQLt.Run 'MyInnerTestsA.[test should execute outside of transaction]'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +--[@tSQLt:SkipTest]('TODO: tSQLt should handle this, but does not at the moment because of the issue with transactions started within a try-catch block') +CREATE PROCEDURE Run_Methods_Tests.[test produces meaningful error when pre and post transactions counts don't match in NoTransaction test] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTestsB' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTestsB.[test should execute outside of transaction] AS BEGIN TRAN; + '); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'SOMETHING RATHER', @ExpectedSeverity = NULL, @ExpectedState = NULL; + EXEC tSQLt.Run 'MyInnerTestsB.[test should execute outside of transaction]'; + + -- FOR FUTURE DEBUGGING + --BEGIN TRY + -- EXEC tSQLt.Run 'MyInnerTestsB.[test should execute outside of transaction]'; + --END TRY + --BEGIN CATCH + -- SELECT * FROM fn_dblog(NULL,NULL) WHERE [Transaction ID] = (SELECT LL.[Transaction ID] FROM fn_dblog(NULL,NULL) LL JOIN sys.dm_tran_current_transaction AS DTCT ON DTCT.transaction_id = LL.[Xact ID]); + --END CATCH; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +/*-- + Transaction Tests to be considered + + 1. NoTransaction test starts a transaction + 1a. The transaction is commitable (THIS FEELS UNLIKELY GIVEN OUR RESEARCH.) + 1b. The transaction is not commitable + + 2. Transaction test rolls-back tSQLt transaction(s) + 2a. No transaction after test (ROLLBACK, but no new transactions created within the test) + 2b. New transaction after test (ROLLBACK, but new one created within the test) + + 3. Transaction test commits tSQLt transactions(s) + 3a. No transaction after test (COMMIT, but no new transactions created within the test) + 3b. New transaction after test (COMMIT, but new one created within the test) + + 4. Transaction test renders transaction uncommitable (Won't be able to write to anything, including tSQLt.TestResults or a variety of temp tables.) + + - currently AssertNoSideEffects causes additional problems if executed inside an uncommittable transaction (It tries to write to a new temp table) <-- does this need to be changed? + - do existing tests already cover some of the scenarios described above? + +--*/ diff --git a/Tests/SetSummaryErrorTests.class.sql b/Tests/SetSummaryErrorTests.class.sql new file mode 100644 index 000000000..acab97698 --- /dev/null +++ b/Tests/SetSummaryErrorTests.class.sql @@ -0,0 +1,47 @@ +EXEC tSQLt.NewTestClass 'SetSummaryErrorTests'; +GO +CREATE PROCEDURE SetSummaryErrorTests.CreateTestsSuiteWithFailingTestAndSetDefaultResultFormatter +AS +BEGIN + EXEC tSQLt.SetTestResultFormatter 'tSQLt.DefaultResultFormatter'; + EXEC tSQLt.NewTestClass 'SetSummaryErrorTestsTests'; + EXEC('CREATE PROCEDURE SetSummaryErrorTestsTests.TestPassing AS RETURN 0;'); + EXEC('CREATE PROCEDURE SetSummaryErrorTestsTests.TestFailing AS EXEC tSQLt.Fail;'); +END; +GO +CREATE PROCEDURE SetSummaryErrorTests.[test suppresses the error in the summary if set to 0] +AS +BEGIN + EXEC SetSummaryErrorTests.CreateTestsSuiteWithFailingTestAndSetDefaultResultFormatter; + EXEC tSQLt.SetSummaryError @SummaryError=0; + EXEC tSQLt.ExpectNoException; + EXEC tSQLt.Run @TestName='SetSummaryErrorTestsTests'; +END; +GO +CREATE PROCEDURE SetSummaryErrorTests.[test doesn't suppress the error in the summary if not set] +AS +BEGIN + EXEC SetSummaryErrorTests.CreateTestsSuiteWithFailingTestAndSetDefaultResultFormatter; + + DELETE FROM tSQLt.Private_Configurations WHERE Name = 'SummaryError'; + + EXEC tSQLt.ExpectException @ExpectedMessagePattern='Test Case Summary:%'; + EXEC tSQLt.Run @TestName='SetSummaryErrorTestsTests'; +END; +GO +CREATE PROCEDURE SetSummaryErrorTests.[test doesn't suppress the error in the summary if set to 1] +AS +BEGIN + EXEC SetSummaryErrorTests.CreateTestsSuiteWithFailingTestAndSetDefaultResultFormatter; + EXEC tSQLt.SetSummaryError @SummaryError=1; + EXEC tSQLt.ExpectException @ExpectedMessagePattern='Test Case Summary:%'; + EXEC tSQLt.Run @TestName='SetSummaryErrorTestsTests'; +END; +GO +CREATE PROCEDURE SetSummaryErrorTests.[test errors if @SummaryError NOT IN (0,1)] +AS +BEGIN + EXEC tSQLt.ExpectException @ExpectedMessagePattern='@SummaryError has to be 0 or 1, but it was:%'; + EXEC tSQLt.SetSummaryError @SummaryError=111; +END; +GO diff --git a/Tests/SpyProcedureTests.class.sql b/Tests/SpyProcedureTests.class.sql index d94e9c392..d3e138cc5 100644 --- a/Tests/SpyProcedureTests.class.sql +++ b/Tests/SpyProcedureTests.class.sql @@ -351,6 +351,30 @@ BEGIN END; GO +CREATE PROC SpyProcedureTests.[test SpyProcedure handles procedure and schema names with single quotes] +AS +BEGIN + DECLARE @ErrorRaised INT; SET @ErrorRaised = 0; + + EXEC('CREATE SCHEMA [Tes''tSchema];'); + EXEC('CREATE PROC [Tes''tSchema].[Spye''e Proc] @testParam1 INT AS RETURN 0;'); + + EXEC tSQLt.SpyProcedure '[Tes''tSchema].[Spye''e Proc]'; + + DECLARE @InnerProcedure VARCHAR(MAX) = '[Tes''tSchema].[Spye''e Proc]'; + EXEC @InnerProcedure 27; + + SELECT testParam1 + INTO #Actual + FROM [Tes'tSchema].[Spye'e Proc_SpyProcedureLog]; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(27); + + EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; +END; +GO + CREATE PROC SpyProcedureTests.[test SpyProcedure calls tSQLt.Private_RenameObjectToUniqueName on original proc] AS BEGIN @@ -545,6 +569,30 @@ BEGIN EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO +CREATE PROC SpyProcedureTests.[test SpyProcedure handles length, precision, and scale correctly] + AS +BEGIN + EXEC('CREATE PROC dbo.InnerProcedure( + @LENGTH1 VARCHAR(42) , + @LENGTH2 VARCHAR(MAX) , + @PRECISION_SCALE NUMERIC(21, 13) + ) + AS BEGIN RETURN 0; END'); + SELECT name, parameter_id, system_type_id, user_type_id, max_length, precision, scale + INTO #Expected + FROM sys.parameters + WHERE object_id = OBJECT_ID('dbo.InnerProcedure'); + + EXEC tSQLt.SpyProcedure 'dbo.InnerProcedure' + + SELECT name, parameter_id, system_type_id, user_type_id, max_length, precision, scale + INTO #Actual + FROM sys.parameters + WHERE object_id = OBJECT_ID('dbo.InnerProcedure'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO CREATE PROC SpyProcedureTests.[test SpyProcedure fails with error if spyee has more than 1020 parameters] AS BEGIN @@ -599,7 +647,7 @@ AS BEGIN CREATE TYPE SpyProcedureTests.ProcedureAndType AS TABLE(id INT); EXEC('CREATE PROC SpyProcedureTests.ProcedureAndType AS RETURN;'); -SELECT OBJECT_ID('SpyProcedureTests.ProcedureAndType'),TYPE_ID('SpyProcedureTests.ProcedureAndType'); +--SELECT OBJECT_ID('SpyProcedureTests.ProcedureAndType'),TYPE_ID('SpyProcedureTests.ProcedureAndType'); EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.ProcedureAndType'; EXEC ('EXEC SpyProcedureTests.ProcedureAndType'); @@ -616,3 +664,487 @@ SELECT OBJECT_ID('SpyProcedureTests.ProcedureAndType'),TYPE_ID('SpyProcedureTest EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO +CREATE TYPE SpyProcedureTests.TableType1001 AS TABLE(SomeInt INT, SomeVarChar VARCHAR(10)); +GO +CREATE PROC SpyProcedureTests.[test SpyProcedure can have a table type parameter] +AS +BEGIN + EXEC('CREATE PROC SpyProcedureTests.InnerProcedure @P1 SpyProcedureTests.TableType1001 READONLY AS EXEC tSQLt.Fail ''InnerProcedure was executed;'''); + + EXEC tSQLt.SpyProcedure 'SpyProcedureTests.InnerProcedure'; + + DECLARE @TableParameter SpyProcedureTests.TableType1001; + INSERT INTO @TableParameter(SomeInt,SomeVarChar)VALUES(10,'S1'); + INSERT INTO @TableParameter(SomeInt,SomeVarChar)VALUES(202,'V2'); + INSERT INTO @TableParameter(SomeInt,SomeVarChar)VALUES(3303,'C3'); + + DECLARE @InnerProcedure VARCHAR(MAX);SET @InnerProcedure = 'SpyProcedureTests.InnerProcedure' + EXEC @InnerProcedure @P1 = @TableParameter; + + SELECT RowNode.value('SomeInt[1]','INT') SomeInt,RowNode.value('SomeVarChar[1]','VARCHAR(10)') SomeVarChar + INTO #Actual + FROM SpyProcedureTests.InnerProcedure_SpyProcedureLog + CROSS APPLY P1.nodes('P1/row') AS N(RowNode); + + SELECT * + INTO #Expected + FROM @TableParameter AS TP; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROC SpyProcedureTests.[test Private_GenerateCreateProcedureSpyStatement does not create log table when @LogTableName is NULL] +AS +BEGIN + EXEC('CREATE PROC dbo.OriginalInnerProcedure AS RETURN;'); + + DECLARE @ProcedureObjectId INT = OBJECT_ID('dbo.OriginalInnerProcedure'); + + DECLARE @CreateProcedureStatement NVARCHAR(MAX); + DECLARE @CreateLogTableStatement NVARCHAR(MAX); + + EXEC tSQLt.Private_GenerateCreateProcedureSpyStatement + @ProcedureObjectId = @ProcedureObjectId, + @OriginalProcedureName = 'dbo.OriginalInnerProcedure', + @LogTableName = NULL, + @CommandToExecute = NULL, + @CallOriginal = 0, + @CreateProcedureStatement = @CreateProcedureStatement OUT, + @CreateLogTableStatement = @CreateLogTableStatement OUT; + + EXEC tSQLt.AssertEqualsString @Expected = NULL, @Actual = @CreateLogTableStatement; +END; +GO +GO +CREATE PROC SpyProcedureTests.[test Private_CreateProcedureSpy does create spy when @LogTableName is NULL] +AS +BEGIN + EXEC('CREATE PROC dbo.OriginalInnerProcedure AS RETURN;'); /* This is the simulated rename of dbo.SpiedInnerProcedure */ + + DECLARE @ProcedureObjectId INT = OBJECT_ID('dbo.OriginalInnerProcedure'); + + DECLARE @CreateProcedureStatement NVARCHAR(MAX); + DECLARE @CreateLogTableStatement NVARCHAR(MAX); + + EXEC tSQLt.Private_GenerateCreateProcedureSpyStatement + @ProcedureObjectId = @ProcedureObjectId, + @OriginalProcedureName = 'dbo.SpiedInnerProcedure', + @LogTableName = NULL, + @CommandToExecute = NULL, + @CallOriginal = 0, + @CreateProcedureStatement = @CreateProcedureStatement OUT, + @CreateLogTableStatement = @CreateLogTableStatement OUT; + + EXEC(@CreateProcedureStatement); + + EXEC tSQLt.AssertObjectExists @ObjectName = 'dbo.SpiedInnerProcedure'; + +END; +GO +CREATE PROC SpyProcedureTests.[test SpyProcedure works with CLR procedures] +AS +BEGIN + EXEC('CREATE PROC dbo.InnerProcedure @expectedCommand NVARCHAR(MAX), @actualCommand NVARCHAR(MAX) AS EXTERNAL NAME tSQLtCLR.[tSQLtCLR.StoredProcedures].AssertResultSetsHaveSameMetaData;'); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'dbo.InnerProcedure'; + + DECLARE @ProcName NVARCHAR(MAX) = 'dbo.InnerProcedure'; + EXEC @ProcName @expectedCommand = 'Select 1 [Int]', @actualCommand = 'Select ''c'' [char]'; + + SELECT expectedCommand, actualCommand INTO #Actual FROM dbo.InnerProcedure_SpyProcedureLog; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('Select 1 [Int]', 'Select ''c'' [char]'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROC SpyProcedureTests.[test new Procedure Spy is marked as tSQLt.IsTempObject] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS RETURN;'); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1'; + + SELECT name, value + INTO #Actual + FROM sys.extended_properties + WHERE class_desc = 'OBJECT_OR_COLUMN' + AND major_id = OBJECT_ID('SpyProcedureTests.TempProcedure1') + AND name = 'tSQLt.IsTempObject'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('tSQLt.IsTempObject', 1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROC SpyProcedureTests.[test SpyProcedure calls tSQLt.Private_MarktSQLtTempObject on new objects] +AS +BEGIN + DECLARE @OriginalObjectId INT = OBJECT_ID('tSQLt.Private_MarktSQLtTempObject'); + + EXEC tSQLt.SpyProcedure @ProcedureName = '[tSQLt].[Private_MarktSQLtTempObject]'; + --We are testing that SpyProcedure calls this ^^ after spying this ^^, so this line serves as prep and action. + + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('[tSQLt].[Private_MarktSQLtTempObject]', N'PROCEDURE', OBJECT_NAME(@OriginalObjectId)), + ('[tSQLt].[Private_MarktSQLtTempObject_SpyProcedureLog]', N'TABLE', NULL); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test new SpyProcedureLog table is marked as tSQLt.IsTempObject] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS RETURN;'); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1'; + + SELECT name, value + INTO #Actual + FROM sys.extended_properties + WHERE class_desc = 'OBJECT_OR_COLUMN' + AND major_id = OBJECT_ID('SpyProcedureTests.TempProcedure1_SpyProcedureLog') + AND name = 'tSQLt.IsTempObject'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('tSQLt.IsTempObject', 1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test can handle existing SpyProcedureLog table] +AS +BEGIN + CREATE TABLE SpyProcedureTests.TempProcedure1_SpyProcedureLog ([Please don't do this] INT, [but just in case, we can handle it] NVARCHAR(MAX)); + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS RETURN;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls the original procedure if @CallOriginal = 1] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + EXEC('SpyProcedureTests.TempProcedure1'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'SpyProcedureTests.TempProcedure1 called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test does not call the original procedure if @CallOriginal = 0] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 0; + + EXEC('SpyProcedureTests.TempProcedure1'); + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test does not call the original procedure if @CallOriginal = NULL] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = NULL; + + EXEC('SpyProcedureTests.TempProcedure1'); + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test does not call the original procedure if @CallOriginal is not specified] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1'; + + EXEC('SpyProcedureTests.TempProcedure1'); + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls the original procedure if @CallOriginal = 1 even if schema or procedure name require quoting] +AS +BEGIN + EXEC('CREATE SCHEMA [Inner Test Schema];'); + EXEC('CREATE PROCEDURE [Inner Test Schema].[Temp Procedure 1] AS BEGIN INSERT INTO #Actual VALUES (''[Inner Test Schema].[Temp Procedure 1] called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = '[Inner Test Schema].[Temp Procedure 1]', @CallOriginal = 1; + + EXEC('[Inner Test Schema].[Temp Procedure 1]'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'[Inner Test Schema].[Temp Procedure 1] called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROC SpyProcedureTests.[test calls original procedure with parameters if @CallOriginal = 1] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @AnInt INT, @AString NVARCHAR(MAX) AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1(''+CAST(@AnInt AS NVARCHAR(MAX))+'',''+@AString+'') called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + EXEC('EXEC SpyProcedureTests.TempProcedure1 42,''XYZ'';'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'SpyProcedureTests.TempProcedure1(42,XYZ) called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls original procedure with OUTPUT parameters if @CallOriginal = 1] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @AnInt INT OUTPUT, @AString NVARCHAR(MAX) OUTPUT AS BEGIN SELECT @AnInt = 8383, @AString = ''424242''; END;'); + + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + DECLARE @InputOutputInt INT = -17; + DECLARE @InputOutputString NVARCHAR(MAX) = '007'; + DECLARE @ProcedureNameVariableSoWeDoNotGetAWarning NVARCHAR(MAX) = 'SpyProcedureTests.TempProcedure1'; + EXEC @ProcedureNameVariableSoWeDoNotGetAWarning @InputOutputInt OUT, @InputOutputString OUT; + + SELECT @InputOutputInt AS AnInt, @InputOutputString AS AString INTO #Actual; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(8383, '424242'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls original procedure with cursor parameters if @CallOriginal = 1] +AS +BEGIN + EXEC(' + CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @Cursor1 CURSOR VARYING OUTPUT, @NotACursor INT, @Cursor2 CURSOR VARYING OUTPUT + AS + BEGIN + DECLARE @FirstInt INT; + DECLARE @SecondInt INT; + OPEN @Cursor1; + FETCH NEXT FROM @Cursor1 INTO @FirstInt; + CLOSE @Cursor1; + DEALLOCATE @Cursor1; + OPEN @Cursor2; + FETCH NEXT FROM @Cursor2 INTO @SecondInt; + CLOSE @Cursor2; + DEALLOCATE @Cursor2; + INSERT INTO #Actual VALUES(@FirstInt, @SecondInt, @NotACursor); + END;' + ); + CREATE TABLE #Actual (intA INT, intB INT, intC INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + DECLARE @InputOnlyInt INT = 17; + DECLARE @Cursor11 CURSOR; SET @Cursor11 = CURSOR FOR SELECT 36; + DECLARE @Cursor22 CURSOR; SET @Cursor22 = CURSOR FOR SELECT 42; + DECLARE @ProcedureNameVariableSoWeDoNotGetAWarning NVARCHAR(MAX) = 'SpyProcedureTests.TempProcedure1'; + + EXEC @ProcedureNameVariableSoWeDoNotGetAWarning @Cursor11, @InputOnlyInt, @Cursor22; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(36, 42, 17); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE TYPE SpyProcedureTests.Type1 AS TABLE(A INT); +GO +CREATE PROC SpyProcedureTests.[test calls original procedure with table valued parameters if @CallOriginal = 1] +AS +BEGIN + EXEC(' + CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @Table1 SpyProcedureTests.Type1 READONLY, @NotATable INT, @Table2 SpyProcedureTests.Type1 READONLY + AS + BEGIN + DECLARE @FirstInt INT = (SELECT A FROM @Table1); + DECLARE @SecondInt INT = (SELECT A FROM @Table2); + INSERT INTO #Actual VALUES(@FirstInt, @SecondInt, @NotATable); + END;' + ); + CREATE TABLE #Actual (intA INT, intB INT, intC INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + EXEC(' + DECLARE @InputOnlyInt INT = 17; + DECLARE @Table11 AS SpyProcedureTests.Type1; INSERT INTO @Table11 VALUES(36); + DECLARE @Table22 AS SpyProcedureTests.Type1; INSERT INTO @Table22 VALUES(42); + + DECLARE @ProcedureNameVariableSoWeDoNotGetAWarning NVARCHAR(MAX) = ''SpyProcedureTests.TempProcedure1''; + EXEC @ProcedureNameVariableSoWeDoNotGetAWarning @Table11, @InputOnlyInt, @Table22; + '); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(36, 42, 17); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls the original procedure after @CommandToExecute if @CallOriginal = 1] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CommandToExecute='INSERT INTO #Actual VALUES (''CommandToExecute called'');', @CallOriginal = 1; + + EXEC('SpyProcedureTests.TempProcedure1'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'CommandToExecute called'),(2,'SpyProcedureTests.TempProcedure1 called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test @SpyProcedureOriginalObjectName contains original proc name inside spy] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @I INT = NULL AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called'', @I); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX), I INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CommandToExecute='EXEC @SpyProcedureOriginalObjectName 42;'; + + EXEC('SpyProcedureTests.TempProcedure1'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'SpyProcedureTests.TempProcedure1 called',42); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test @SpyProcedureOriginalObjectName contains original proc name inside spy even if quoting is required] +AS +BEGIN + EXEC('CREATE SCHEMA [Inner Test.Schema 1];'); + EXEC('CREATE PROCEDURE [Inner Test.Schema 1].[Temp Proc.edure 1] @I INT = NULL AS BEGIN INSERT INTO #Actual VALUES (''[Inner Test.Schema 1].[Temp Proc.edure 1] called'', @I); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX), I INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = '[Inner Test.Schema 1].[Temp Proc.edure 1]', @CommandToExecute='EXEC @SpyProcedureOriginalObjectName 42;'; + + EXEC('[Inner Test.Schema 1].[Temp Proc.edure 1]'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'[Inner Test.Schema 1].[Temp Proc.edure 1] called',42); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test @SpyProcedureOriginalObjectName contains original proc name even if it has single quotes, dots, or spaces] +AS +BEGIN + EXEC('CREATE SCHEMA [I''nn''er Test.Schema 1];'); + EXEC('CREATE PROCEDURE [I''nn''er Test.Schema 1].[T''emp Proc.edure 1] @I INT = NULL + AS + BEGIN + INSERT INTO #Actual VALUES (''[I''''nn''''er Test.Schema 1].[T''''emp Proc.edure 1] called'', @I); + END;' + ); + + CREATE TABLE #Actual (Msg NVARCHAR(MAX), I INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = '[I''nn''er Test.Schema 1].[T''emp Proc.edure 1]', @CommandToExecute='EXEC @SpyProcedureOriginalObjectName 42;'; + + + EXEC('[I''nn''er Test.Schema 1].[T''emp Proc.edure 1]'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('[I''nn''er Test.Schema 1].[T''emp Proc.edure 1] called',42); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls the original procedure even if @CommandToExecute contains inline comment] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CommandToExecute='DECLARE @II INT = 1;--PRINT @II;', @CallOriginal = 1; + + EXEC('SpyProcedureTests.TempProcedure1'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'SpyProcedureTests.TempProcedure1 called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + diff --git a/Tests/tSQLt_test_TableToText.class.sql b/Tests/TableToTextTests.class.sql similarity index 60% rename from Tests/tSQLt_test_TableToText.class.sql rename to Tests/TableToTextTests.class.sql index cfdd341e8..a84aba4f5 100644 --- a/Tests/tSQLt_test_TableToText.class.sql +++ b/Tests/TableToTextTests.class.sql @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -EXEC tSQLt.NewTestClass 'tSQLtPrivate_test'; +EXEC tSQLt.NewTestClass 'TableToTextTests'; GO -CREATE PROC tSQLtPrivate_test.[test TableToText throws exception if table does not exist] +CREATE PROCEDURE TableToTextTests.[test TableToText throws exception if table does not exist] AS BEGIN @@ -24,7 +24,7 @@ BEGIN BEGIN TRY DECLARE @r NVARCHAR(MAX); - SET @r = tSQLt.Private::TableToString('DoesNotExist', '', NULL); + EXEC tSQLt.TableToText @r OUT, 'DoesNotExist', '', NULL; END TRY BEGIN CATCH SET @err = ERROR_MESSAGE(); @@ -37,28 +37,34 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText throws exception if tablename is NULL] +CREATE PROCEDURE TableToTextTests.[test TableToText throws exception if tablename is NULL] AS BEGIN + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%@TableName cannot be NULL%' - DECLARE @err NVARCHAR(MAX); SET @err = 'No Exception occurred!'; + DECLARE @r NVARCHAR(MAX); + EXEC tSQLt.TableToText @r OUT, NULL, '', NULL; + + -- RETURN; + + -- DECLARE @err NVARCHAR(MAX); SET @err = 'No Exception occurred!'; - BEGIN TRY - DECLARE @r NVARCHAR(MAX); - SET @r = tSQLt.Private::TableToString(NULL, '', NULL); - END TRY - BEGIN CATCH - SET @err = ERROR_MESSAGE(); - END CATCH + -- BEGIN TRY + -- DECLARE @r NVARCHAR(MAX); + -- EXEC tSQLt.TableToText @r OUT, NULL, '', NULL; + -- END TRY + -- BEGIN CATCH + -- SET @err = ERROR_MESSAGE(); + -- END CATCH - IF @err NOT LIKE '%Object name cannot be NULL%' - BEGIN - EXEC tSQLt.Fail 'Unexpected error message was: ', @err; - END; + -- IF @err NOT LIKE '%Object name cannot be NULL%' + -- BEGIN + -- EXEC tSQLt.Fail 'Unexpected error message was: ', @err; + -- END; END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one column #table] AS BEGIN SELECT * @@ -66,7 +72,7 @@ BEGIN FROM (SELECT 1) AS x(y); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('[dbo].[DoesExist]', '', NULL); + EXEC tSQLt.TableToText @result OUT, '[dbo].[DoesExist]', '', NULL; EXEC tSQLt.AssertEqualsString '|y| +-+ @@ -74,7 +80,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one damn short column] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one damn short column] AS BEGIN SELECT '' [ ] @@ -82,7 +88,7 @@ BEGIN FROM (SELECT 1) AS x(y); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '| | +-+ @@ -90,33 +96,33 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for a weird column name] +CREATE PROCEDURE TableToTextTests.[test TableToText works for a weird column name] AS BEGIN DECLARE @result NVARCHAR(MAX); DECLARE @cmd NVARCHAR(MAX); SET @cmd =' - CREATE TABLE #DoesExist(['+CHAR(8)+''']]] VARCHAR(1));INSERT INTO #DoesExist VALUES(''''); - SET @result = tSQLt.Private::TableToString(''#DoesExist'', '''', NULL); + CREATE TABLE #DoesExist(['+CHAR(9)+''']]] VARCHAR(1));INSERT INTO #DoesExist VALUES(''''); + EXEC tSQLt.TableToText @result OUT, ''#DoesExist'', '''', NULL; '; EXEC sp_executesql @cmd,N'@result NVARCHAR(MAX) OUT',@result OUT; DECLARE @expected NVARCHAR(MAX); - SET @expected ='|'+CHAR(8)+''']| + SET @expected ='|'+CHAR(9)+''']| +---+ | |'; EXEC tSQLt.AssertEqualsString @expected, @result; END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for column names starting and ending with square brackets] +CREATE PROCEDURE TableToTextTests.[test TableToText works for column names starting and ending with square brackets] AS BEGIN DECLARE @result NVARCHAR(MAX); DECLARE @cmd NVARCHAR(MAX); SET @cmd =' CREATE TABLE #DoesExist([[a]]] VARCHAR(1));INSERT INTO #DoesExist VALUES(''''); - SET @result = tSQLt.Private::TableToString(''#DoesExist'', '''', NULL); + EXEC tSQLt.TableToText @result OUT, ''#DoesExist'', '''', NULL; '; EXEC sp_executesql @cmd,N'@result NVARCHAR(MAX) OUT',@result OUT; @@ -128,7 +134,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one BIGINT column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one BIGINT column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -140,7 +146,7 @@ BEGIN SELECT (POWER(CAST(-2 AS BIGINT),63)) T; DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +--------------------+ @@ -149,7 +155,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one TEXT column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one TEXT column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -158,7 +164,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES('This is my text value'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +---------------------+ @@ -166,7 +172,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one NTEXT column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one NTEXT column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -175,7 +181,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES(N'This is my text value'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +---------------------+ @@ -183,7 +189,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one DOUBLE column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one DOUBLE column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -191,7 +197,7 @@ BEGIN ); INSERT INTO #DoesExist (T)VALUES(1.712345612345610E+308); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +----------------------+ @@ -199,7 +205,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one DECIMAL(38, 9) column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one DECIMAL(38, 9) column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -208,7 +214,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES('12345678901234567890123456789.123456789'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +---------------------------------------+ @@ -216,7 +222,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one ROWVERSION column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one ROWVERSION column #table] AS BEGIN DECLARE @rowid ROWVERSION; @@ -229,7 +235,7 @@ BEGIN SELECT @rowid = T FROM #DoesExist; DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; DECLARE @expected NVARCHAR(MAX); SET @expected = '|T | @@ -258,7 +264,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one UNIQUEID column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one UNIQUEID column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -267,7 +273,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES('d7b868c6-c16e-443d-9af9-b23cf83bec0b'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +------------------------------------+ @@ -275,7 +281,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one XML column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one XML column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -284,7 +290,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES('d1d2'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +----------------------------------------+ @@ -292,7 +298,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one DATETIME column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one DATETIME column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -301,7 +307,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES('2001-10-13T12:34:56.787'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +-----------------------+ @@ -309,7 +315,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one SMALLDATETIME column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one SMALLDATETIME column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -318,7 +324,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES('2001-10-13T15:34:56.787'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +----------------+ @@ -326,7 +332,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one VARCHAR(MAX)>8000 column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one VARCHAR(MAX)>8000 column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -335,7 +341,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES(REPLICATE(CAST('*' AS VARCHAR(MAX)),8001)); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; DECLARE @expected NVARCHAR(MAX); SELECT @expected = '|T'+REPLICATE(' ',154)+'| @@ -346,7 +352,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one IMAGE column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one IMAGE column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -355,7 +361,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES(CAST(REPLICATE(CAST('*' AS VARCHAR(MAX)),8001) AS VARBINARY(MAX))); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; DECLARE @expected NVARCHAR(MAX); SELECT @expected = '|T'+REPLICATE(' ',154)+'| @@ -366,7 +372,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one SQL_VARIANT column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one SQL_VARIANT column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -375,7 +381,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES('hello'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; DECLARE @expected NVARCHAR(MAX); SELECT @expected = '|T | @@ -386,7 +392,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one VARBINARY(MAX) column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one VARBINARY(MAX) column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -395,7 +401,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES(0xfedcba9876543210); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +------------------+ @@ -403,7 +409,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one BINARY(90) column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one BINARY(90) column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -412,7 +418,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES(0x111213141516171819102122232425262728292031323334353637383930414243444546474849405152535455565758595061626364656667686960717273747576777879708182838485868788898091929394959697989990); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +-----------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -420,7 +426,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one CHAR(155) column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one CHAR(155) column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -429,7 +435,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES('12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +-----------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -437,7 +443,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one CHAR(156) column #table] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one CHAR(156) column #table] AS BEGIN CREATE TABLE #DoesExist( @@ -446,7 +452,7 @@ BEGIN INSERT INTO #DoesExist (T)VALUES('123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456'); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|T | +-----------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -455,7 +461,7 @@ END; GO -- Maximum display length is greater than maximum column length; however, once we make the display length configurable, we'll need a test LIKE this again ---CREATE PROC tSQLtPrivate_test.[test TableToText works for one long named column #table] +--CREATE PROCEDURE TableToTextTests.[test TableToText works for one long named column #table] --AS --BEGIN -- CREATE TABLE #DoesExist( @@ -464,7 +470,7 @@ GO -- INSERT INTO #DoesExist (T12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345)VALUES('1234567890123456789012345678901234567890123456789012345'); -- DECLARE @result NVARCHAR(MAX); --- SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); +-- EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; -- EXEC tSQLt.AssertEqualsString '|T123456789012345678901234<...>1234567890123456789012345| --+-------------------------------------------------------+ @@ -472,7 +478,7 @@ GO --END; --GO -CREATE PROC tSQLtPrivate_test.[test TableToText works for one column #table with several rows] +CREATE PROCEDURE TableToTextTests.[test TableToText works for one column #table with several rows] AS BEGIN SELECT no @@ -480,7 +486,7 @@ BEGIN FROM tSQLt.F_Num(4); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; IF (ISNULL(@result,'') NOT LIKE '|no| +--+ @@ -499,7 +505,7 @@ END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText works if @OrderBy IS NULL] +CREATE PROCEDURE TableToTextTests.[test TableToText works if @OrderBy IS NULL] AS BEGIN SELECT no @@ -507,7 +513,7 @@ BEGIN FROM tSQLt.F_Num(4); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', NULL, NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', NULL, NULL; IF (ISNULL(@result,'') NOT LIKE '|no| +--+ @@ -525,7 +531,7 @@ OR (ISNULL(@result,'') NOT LIKE '%4%') END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText orders by @orderBy] +CREATE PROCEDURE TableToTextTests.[test TableToText orders by @orderBy] AS BEGIN SELECT no @@ -533,7 +539,7 @@ BEGIN FROM tSQLt.F_Num(4); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist','10-no+10*(no%2)', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '10-no+10*(no%2)', NULL; EXEC tSQLt.AssertEqualsString '|no| +--+ @@ -544,7 +550,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText with no rows] +CREATE PROCEDURE TableToTextTests.[test TableToText with no rows] AS BEGIN SELECT no @@ -552,14 +558,14 @@ BEGIN FROM tSQLt.F_Num(0); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist', '', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|no| +--+', @result; END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText with several columns and rows] +CREATE PROCEDURE TableToTextTests.[test TableToText with several columns and rows] AS BEGIN SELECT no, 10-no AS FromTen, NULL AS NullCol @@ -567,7 +573,7 @@ BEGIN FROM tSQLt.F_Num(4); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist','no', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', 'no', NULL; EXEC tSQLt.AssertEqualsString '|no|FromTen|NullCol| +--+-------+-------+ @@ -578,7 +584,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText with aliased column names] +CREATE PROCEDURE TableToTextTests.[test TableToText with aliased column names] AS BEGIN SELECT no, 10-no AS FromTen, NULL AS NullCol @@ -586,7 +592,8 @@ BEGIN FROM tSQLt.F_Num(4); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist','no','[Col1],[Col2],[Col3]'); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', 'no', '[Col1],[Col2],[Col3]'; + EXEC tSQLt.TableToText @result OUT, '#DoesExist','no','[Col1],[Col2],[Col3]'; EXEC tSQLt.AssertEqualsString '|Col1|Col2|Col3 | +----+----+------+ @@ -597,7 +604,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test NULL values with short column name] +CREATE PROCEDURE TableToTextTests.[test NULL values with short column name] AS BEGIN SELECT NULL AS n @@ -605,7 +612,7 @@ BEGIN FROM tSQLt.F_Num(4); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist','', NULL); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; EXEC tSQLt.AssertEqualsString '|n | +------+ @@ -616,7 +623,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText with 100 columns] +CREATE PROCEDURE TableToTextTests.[test TableToText with 100 columns] AS BEGIN /* @@ -634,8 +641,8 @@ PRINT @cols; FROM tSQLt.F_Num(4); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('#DoesExist','C001', NULL); - + EXEC tSQLt.TableToText @result OUT, '#DoesExist', 'C001', NULL; + EXEC tSQLt.AssertEqualsString '|C001|C002|C003|C004|C005|C006|C007|C008|C009|C010|C011|C012|C013|C014|C015|C016|C017|C018|C019|C020|C021|C022|C023|C024|C025|C026|C027|C028|C029|C030|C031|C032|C033|C034|C035|C036|C037|C038|C039|C040|C041|C042|C043|C044|C045|C046|C047|C048|C049|C050|C051|C052|C053|C054|C055|C056|C057|C058|C059|C060|C061|C062|C063|C064|C065|C066|C067|C068|C069|C070|C071|C072|C073|C074|C075|C076|C077|C078|C079|C080|C081|C082|C083|C084|C085|C086|C087|C088|C089|C090|C091|C092|C093|C094|C095|C096|C097|C098|C099|C100| +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 |16 |17 |18 |19 |20 |21 |22 |23 |24 |25 |26 |27 |28 |29 |30 |31 |32 |33 |34 |35 |36 |37 |38 |39 |40 |41 |42 |43 |44 |45 |46 |47 |48 |49 |50 |51 |52 |53 |54 |55 |56 |57 |58 |59 |60 |61 |62 |63 |64 |65 |66 |67 |68 |69 |70 |71 |72 |73 |74 |75 |76 |77 |78 |79 |80 |81 |82 |83 |84 |85 |86 |87 |88 |89 |90 |91 |92 |93 |94 |95 |96 |97 |98 |99 |100 |101 | @@ -645,7 +652,7 @@ PRINT @cols; END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText removes quotes from columns in column list] +CREATE PROCEDURE TableToTextTests.[test TableToText removes quotes from columns in column list] AS BEGIN SELECT * @@ -653,7 +660,7 @@ BEGIN FROM (SELECT 1,2) AS x(a,b); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('[dbo].[DoesExist]', '', '[a],[b]'); + EXEC tSQLt.TableToText @result OUT, '[dbo].[DoesExist]', '', '[a],[b]'; EXEC tSQLt.AssertEqualsString '|a|b| +-+-+ @@ -661,7 +668,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText handles brackets in column names in column list] +CREATE PROCEDURE TableToTextTests.[test TableToText handles brackets in column names in column list] AS BEGIN SELECT * @@ -669,7 +676,7 @@ BEGIN FROM (SELECT 1,2) AS x([[a]]],[]]b[]); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('[dbo].[DoesExist]', '', '[[a]]],[]]b[]'); + EXEC tSQLt.TableToText @result OUT, '[dbo].[DoesExist]', '', '[[a]]],[]]b[]'; EXEC tSQLt.AssertEqualsString '|[a]|]b[| +---+---+ @@ -677,7 +684,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText handles column name containing ","] +CREATE PROCEDURE TableToTextTests.[test TableToText handles column name containing ","] AS BEGIN SELECT * @@ -685,7 +692,7 @@ BEGIN FROM (SELECT 1,2) AS x(a,[b,c]); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('[dbo].[DoesExist]', '', NULL); + EXEC tSQLt.TableToText @result OUT, '[dbo].[DoesExist]', '', NULL; EXEC tSQLt.AssertEqualsString '|a|b,c| +-+---+ @@ -693,7 +700,7 @@ BEGIN END; GO -CREATE PROC tSQLtPrivate_test.[test TableToText handles column containing "," in column list] +CREATE PROCEDURE TableToTextTests.[test TableToText handles column containing "," in column list] AS BEGIN SELECT * @@ -701,14 +708,169 @@ BEGIN FROM (SELECT 1,2) AS x(a,b); DECLARE @result NVARCHAR(MAX); - SET @result = tSQLt.Private::TableToString('[dbo].[DoesExist]', '', '[a],[b,c]'); + EXEC tSQLt.TableToText @result OUT, '[dbo].[DoesExist]', '', '[a],[b,c]'; EXEC tSQLt.AssertEqualsString '|a|b,c| +-+---+ |1|2 |', @result; END; GO +CREATE PROCEDURE TableToTextTests.[test TableToText works for one DATETIMEOFFSET column #table] +AS +BEGIN + CREATE TABLE #DoesExist( + T DATETIMEOFFSET + ); + INSERT INTO #DoesExist (T)VALUES(CAST('2001-10-13 12:34:56.7891234 +13:24' AS DATETIMEOFFSET)); + + DECLARE @result NVARCHAR(MAX); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; + + EXEC tSQLt.AssertEqualsString '|T | ++----------------------------------+ +|2001-10-13 12:34:56.7891234 +13:24|', @result; +END; +GO + +CREATE PROCEDURE TableToTextTests.[test TableToText works for one DATETIME2 column #table] +AS +BEGIN + CREATE TABLE #DoesExist( + T DATETIME2 + ); + INSERT INTO #DoesExist (T)VALUES(CAST('2001-10-13T12:34:56.7891234' AS DATETIME2)); + + DECLARE @result NVARCHAR(MAX); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; + + EXEC tSQLt.AssertEqualsString '|T | ++---------------------------+ +|2001-10-13 12:34:56.7891234|', @result; +END; +GO + +CREATE PROCEDURE TableToTextTests.[test TableToText works for one TIME column #table] +AS +BEGIN + CREATE TABLE #DoesExist( + T TIME + ); + INSERT INTO #DoesExist (T)VALUES('2001-10-13T12:34:56.7871234'); + + DECLARE @result NVARCHAR(MAX); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; + + EXEC tSQLt.AssertEqualsString '|T | ++----------------+ +|12:34:56.7871234|', @result; +END; +GO + +CREATE PROCEDURE TableToTextTests.[test TableToText works for one DATE column #table] +AS +BEGIN + CREATE TABLE #DoesExist( + T DATE + ); + INSERT INTO #DoesExist (T)VALUES('2001-10-13T12:34:56.787'); + + DECLARE @result NVARCHAR(MAX); + EXEC tSQLt.TableToText @result OUT, '#DoesExist', '', NULL; + + EXEC tSQLt.AssertEqualsString '|T | ++----------+ +|2001-10-13|', @result; +END; +GO + +CREATE PROCEDURE TableToTextTests.[test TableToText works for CLR datatype that is not in tempdb] +AS +BEGIN + IF(OBJECT_ID('TableToTextTests.DoesExist')IS NOT NULL)DROP TABLE TableToTextTests.DoesExist; + CREATE TABLE TableToTextTests.DoesExist( + T tSQLt_testutil.DataTypeByteOrdered + ); + INSERT INTO TableToTextTests.DoesExist (T)VALUES('42'); + + DECLARE @result NVARCHAR(MAX); + EXEC tSQLt.TableToText @result OUT, 'TableToTextTests.DoesExist', '', NULL; + + EXEC tSQLt.AssertEqualsString '|T | ++--------------------------+ +|<>|', @result; +END; +GO +CREATE PROCEDURE TableToTextTests.[test TableToText does not add or remove objects] +AS +BEGIN + IF(OBJECT_ID('TableToTextTests.DoesExist')IS NOT NULL)DROP TABLE TableToTextTests.DoesExist; + CREATE TABLE TableToTextTests.DoesExist( + T INT + ); + + SELECT object_id,name INTO #PreState FROM sys.objects; + + DECLARE @result NVARCHAR(MAX); + EXEC tSQLt.TableToText @result OUT, 'TableToTextTests.DoesExist', '', NULL; + SELECT + CASE WHEN O.object_id IS NULL THEN 'DELETED' ELSE 'ADDED' END [action], + CASE WHEN O.object_id IS NULL THEN P.object_id ELSE O.object_id END object_id, + CASE WHEN O.object_id IS NULL THEN P.name ELSE O.name END [name] + INTO #Actual + FROM sys.objects O + FULL JOIN #PreState P + ON O.name = P.name + AND O.object_id = P.object_id + WHERE O.object_id IS NULL + OR P.object_id IS NULL; + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +CREATE PROCEDURE TableToTextTests.[test TableToText tags temporary object] +AS +BEGIN + IF(OBJECT_ID('TableToTextTests.DoesExist')IS NOT NULL)DROP TABLE TableToTextTests.DoesExist; + CREATE TABLE TableToTextTests.DoesExist( + T INT + ); + + DECLARE @cmd NVARCHAR(MAX) = + 'ALTER PROCEDURE tSQLt.Private_MarktSQLtTempObject '+ + '@ObjectName NVARCHAR(MAX),@ObjectType NVARCHAR(MAX),@NewNameOfOriginalObject NVARCHAR(4000) = NULL '+ + 'AS BEGIN '+ + ' INSERT INTO [#TableToTextTests.PostState] SELECT object_id,name FROM sys.objects;'+ + ' INSERT INTO [#TableToTextTests.Parameters] SELECT OBJECT_ID(@ObjectName),@ObjectType;'+ + 'END;'; + EXEC(@cmd); + + SELECT TOP(0) CAST('' AS INT) ObjectId,CAST('' AS NVARCHAR(MAX)) ObjectType + INTO [#TableToTextTests.Parameters]; + SELECT object_id,name INTO [#TableToTextTests.PreState] FROM sys.objects; + SELECT TOP(0) object_id,name INTO [#TableToTextTests.PostState] FROM sys.objects; + + DECLARE @result NVARCHAR(MAX); + EXEC tSQLt.TableToText @result OUT, 'TableToTextTests.DoesExist', '', NULL; + + SELECT * + INTO #Actual + FROM + ( + SELECT PO.* + FROM [#TableToTextTests.PostState] PO + LEFT JOIN [#TableToTextTests.PreState] PR + ON PO.name = PR.name + AND PO.object_id = PR.object_id + WHERE PR.object_id IS NULL + ) PO + FULL JOIN [#TableToTextTests.Parameters] PA + ON PO.object_id = PA.ObjectId + AND PA.ObjectType = 'TABLE' + WHERE PO.object_id IS NULL + OR PA.ObjectId IS NULL; + + EXEC tSQLt.AssertEmptyTable '#Actual'; ---EXEC tSQLt.Run 'tSQLtPrivate_test'; +END; +GO diff --git a/Tests/TestClassesTest.class.sql b/Tests/TestClassesTest.class.sql new file mode 100644 index 000000000..1f2f75a78 --- /dev/null +++ b/Tests/TestClassesTest.class.sql @@ -0,0 +1,124 @@ +EXEC tSQLt.NewTestClass @ClassName = 'TestClassesTests'; +GO +CREATE PROCEDURE TestClassesTests.[test tSQLt.TestClasses returns a class that is owned by 'tSQLt.TestClass'] +AS +BEGIN + EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; + EXEC ('CREATE SCHEMA tSQLtTestDummyA AUTHORIZATION [tSQLt.TestClass];'); + + SELECT Name + INTO #Actual + FROM tSQLt.TestClasses; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected(Name) VALUES ('tSQLtTestDummyA'); + + EXEC tSQLt.AssertEqualsTable @Expected = '#Expected', @Actual = '#Actual'; +END; +GO +CREATE PROCEDURE TestClassesTests.[test tSQLt.TestClasses returns test classes of all types] +AS +BEGIN + EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; + EXEC ('CREATE SCHEMA tSQLtTestDummyA AUTHORIZATION [tSQLt.TestClass];'); + EXEC ('CREATE SCHEMA tSQLtTestDummyB AUTHORIZATION [tSQLt.TestClass];'); + EXEC ('CREATE SCHEMA tSQLtTestDummyC AUTHORIZATION [tSQLt.TestClass];'); + EXEC tSQLt.NewTestClass 'tSQLtTestDummyD'; + EXEC tSQLt.NewTestClass 'tSQLtTestDummyE'; + + + SELECT Name + INTO #Actual + FROM tSQLt.TestClasses; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected(Name) + VALUES ('tSQLtTestDummyA'), + ('tSQLtTestDummyB'), + ('tSQLtTestDummyC'), + ('tSQLtTestDummyD'), + ('tSQLtTestDummyE'); + + EXEC tSQLt.AssertEqualsTable @Expected = '#Expected', @Actual = '#Actual'; +END; +GO +CREATE PROCEDURE TestClassesTests.[test tSQLt.TestClasses returns no test classes when there are no test classes] +AS +BEGIN + EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; + + SELECT * + INTO #Actual + FROM tSQLt.TestClasses; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + EXEC tSQLt.AssertEqualsTable @Expected = '#Expected', @Actual = '#Actual'; +END; +GO + +CREATE PROCEDURE TestClassesTests.[test tSQLt.TestClasses returns single test class] +AS +BEGIN + EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; + EXEC tSQLt.NewTestClass 'tSQLt_test_dummy_A'; + + SELECT Name + INTO #Actual + FROM tSQLt.TestClasses; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected(Name) VALUES ('tSQLt_test_dummy_A'); + + EXEC tSQLt.AssertEqualsTable @Expected = '#Expected', @Actual = '#Actual'; +END; +GO + +CREATE PROCEDURE TestClassesTests.[test tSQLt.TestClasses returns multiple test classes] +AS +BEGIN + EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; + EXEC tSQLt.NewTestClass 'tSQLt_test_dummy_A'; + EXEC tSQLt.NewTestClass 'tSQLt_test_dummy_B'; + + SELECT Name + INTO #Actual + FROM tSQLt.TestClasses; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected(Name) VALUES ('tSQLt_test_dummy_A'); + INSERT INTO #Expected(Name) VALUES ('tSQLt_test_dummy_B'); + + EXEC tSQLt.AssertEqualsTable @Expected = '#Expected', @Actual = '#Actual'; +END; +GO + +CREATE PROCEDURE TestClassesTests.[test tSQLt.TestClasses returns other important columns] +AS +BEGIN + EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; + EXEC tSQLt.NewTestClass 'tSQLt_test_dummy_A'; + + SELECT Name,SchemaId + INTO #Actual + FROM tSQLt.TestClasses; + + SELECT TOP(0) * + INTO #Expected + FROM #Actual; + + INSERT INTO #Expected(Name, SchemaId) VALUES ('tSQLt_test_dummy_A',SCHEMA_ID('tSQLt_test_dummy_A')); + + EXEC tSQLt.AssertEqualsTable @Expected = '#Expected', @Actual = '#Actual'; +END; +GO diff --git a/Tests/Tests.sqlproj b/Tests/Tests.sqlproj new file mode 100644 index 000000000..49f3673f2 --- /dev/null +++ b/Tests/Tests.sqlproj @@ -0,0 +1,13 @@ + + + + + Tests + {5A1279BA-7094-4A0B-87E9-A1B3637B4F3D} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + 1033, CI + + + + + \ No newline at end of file diff --git a/Tests/Tests.ssmssqlproj b/Tests/Tests.ssmssqlproj deleted file mode 100644 index fbd9eac4f..000000000 --- a/Tests/Tests.ssmssqlproj +++ /dev/null @@ -1,262 +0,0 @@ - - - - - - - 2014-11-24T18:15:21.0565881-05:00 - SQL - Dev_tSQLt - - Windows Authentication - tSQLt_Dev - 15 - 0 - NotSpecified - Microsoft SQL Server Management Studio - Query - - - - - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ApplyConstraintTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ApplyTriggerTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertEmptyTableTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertEqualsStringTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertEqualsTableSchemaTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertEqualsTableTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertEqualsTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertLikeTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertNotEqualsTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertObjectDoesNotExistTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - AssertObjectExistsTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - BootStrapTest.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - DropClassTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ExpectExceptionTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ExpectNoExceptionTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - FailTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - FakeFunctionTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - FakeTableTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - InfoTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - NameAndIdResolution_test.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - NewTestClassTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_NullCellTableTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_RenameObjectToUniqueNameTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_GetFullTypeNameTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_ResetNewTestClassListTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_ScriptIndexTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Private_SqlVariantFormatterTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - RemoveObjectIfExistsTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - RemoveObjectTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - RenameClassTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - ResetTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - Run_Methods_Tests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - StubRecordTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt_test_AssertResultSetsHaveSameMetaData.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - SpyProcedureTests.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt_test.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt_test_ResultSetFilter.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLt_test_TableToText.class.sql - - - 8c91a03d-f9b4-46c0-a305-b5dcc79ff907:Dev_tSQLt:True - Dev_tSQLt - - tSQLtclr_test.class.sql - - - - - - - - \ No newline at end of file diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql new file mode 100644 index 000000000..1c2b8b85d --- /dev/null +++ b/Tests/UndoTestDoublesTests.class.sql @@ -0,0 +1,824 @@ +EXEC tSQLt.NewTestClass 'UndoTestDoublesTests'; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test doesn't fail if there's no test double in the database] +AS +BEGIN + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.UndoTestDoubles; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a faked table] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT ); + DECLARE @OriginalObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test works with names in need of quotes] +AS +BEGIN + EXEC('CREATE SCHEMA [A Random Schema];'); + CREATE TABLE [A Random Schema].[A Simple Table] + ( + Id INT + ); + + DECLARE @OriginalObjectId INT = OBJECT_ID('[A Random Schema].[A Simple Table]'); + + EXEC tSQLt.FakeTable @TableName = '[A Random Schema].[A Simple Table]'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('[A Random Schema].[A Simple Table]'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores many faked tables] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable1 ( Id INT ); + CREATE TABLE UndoTestDoublesTests.aSimpleTable2 ( Id INT ); + CREATE TABLE UndoTestDoublesTests.aSimpleTable3 ( Id INT ); + + SELECT X.TableName, OBJECT_ID('UndoTestDoublesTests.'+X.TableName) ObjectId + INTO #OriginalObjectIds + FROM (VALUES('aSimpleTable1'),('aSimpleTable2'),('aSimpleTable3')) X (TableName); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable1'; + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable2'; + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable3'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT X.TableName, OBJECT_ID('UndoTestDoublesTests.'+X.TableName) ObjectId + INTO #RestoredObjectIds + FROM (VALUES('aSimpleTable1'),('aSimpleTable2'),('aSimpleTable3')) X (TableName); + + EXEC tSQLt.AssertEqualsTable @Expected = '#OriginalObjectIds', @Actual = '#RestoredObjectIds'; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a constraint] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT CONSTRAINT aSimpleTableConstraint CHECK(Id > 0)); + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #OriginalObjectIds + FROM (VALUES('aSimpleTableConstraint')) X (ObjectName); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + EXEC tSQLt.ApplyConstraint @TableName = 'UndoTestDoublesTests.aSimpleTable', @ConstraintName = 'aSimpleTableConstraint'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #RestoredObjectIds + FROM (VALUES('aSimpleTableConstraint')) X (ObjectName); + + EXEC tSQLt.AssertEqualsTable @Expected = '#OriginalObjectIds', @Actual = '#RestoredObjectIds'; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a table that has been faked multiple times] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT ); + + DECLARE @OriginalObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a trigger] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT ); + EXEC('CREATE TRIGGER aSimpleTrigger ON UndoTestDoublesTests.aSimpleTable FOR INSERT AS RETURN;'); + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #OriginalObjectIds + FROM (VALUES('aSimpleTrigger')) X (ObjectName); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + EXEC tSQLt.ApplyTrigger @TableName = 'UndoTestDoublesTests.aSimpleTable', @TriggerName = 'aSimpleTrigger'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #RestoredObjectIds + FROM (VALUES('aSimpleTrigger')) X (ObjectName); + + EXEC tSQLt.AssertEqualsTable @Expected = '#OriginalObjectIds', @Actual = '#RestoredObjectIds'; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.CreateTableWithTriggersAndConstraints + @Number NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + SELECT @cmd = 'CREATE TABLE UndoTestDoublesTests.aSimpleTable0 ( Id INT CONSTRAINT aSimpleTable0C1 CHECK(Id > 9) CONSTRAINT aSimpleTable0PK PRIMARY KEY);'; + SET @cmd = REPLACE(@cmd,'0',@Number);EXEC(@cmd); + + SET @cmd = 'CREATE TRIGGER aSimpleTrigger0i ON UndoTestDoublesTests.aSimpleTable0 FOR INSERT AS RETURN;'; + SET @cmd = REPLACE(@cmd,'0',@Number);EXEC(@cmd); + + SET @cmd = 'CREATE TRIGGER aSimpleTrigger0u ON UndoTestDoublesTests.aSimpleTable0 FOR UPDATE AS RETURN;'; + SET @cmd = REPLACE(@cmd,'0',@Number);EXEC(@cmd); + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints + @Number NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + SELECT @cmd = ' + EXEC tSQLt.FakeTable @TableName=''UndoTestDoublesTests.aSimpleTable0''; + EXEC tSQLt.ApplyConstraint @TableName=''UndoTestDoublesTests.aSimpleTable0'', @ConstraintName = ''aSimpleTable0C1''; + EXEC tSQLt.ApplyConstraint @TableName=''UndoTestDoublesTests.aSimpleTable0'', @ConstraintName = ''aSimpleTable0PK''; + EXEC tSQLt.ApplyTrigger @TableName=''UndoTestDoublesTests.aSimpleTable0'', @TriggerName = ''aSimpleTrigger0i''; + EXEC tSQLt.ApplyTrigger @TableName=''UndoTestDoublesTests.aSimpleTable0'', @TriggerName = ''aSimpleTrigger0u''; + '; + SET @cmd = REPLACE(@cmd,'0',@Number);EXEC(@cmd); + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores objects after multiple fake actions and deletes test doubles] +AS +BEGIN + EXEC UndoTestDoublesTests.CreateTableWithTriggersAndConstraints '1'; + EXEC UndoTestDoublesTests.CreateTableWithTriggersAndConstraints '2'; + EXEC UndoTestDoublesTests.CreateTableWithTriggersAndConstraints '3'; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '1'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '2'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '3'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '1'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '2'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '3'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '1'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '2'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '3'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test tSQLt.Private_RenamedObjectLog is empty after execution] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT ); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + + EXEC tSQLt.UndoTestDoubles; + + EXEC tSQLt.AssertEmptyTable @TableName = 'tSQLt.Private_RenamedObjectLog'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a spied stored procedure] +AS +BEGIN + EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleSSP @Id INT AS RETURN;'); + DECLARE @OriginalObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleSSP'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'UndoTestDoublesTests.aSimpleSSP'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleSSP'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a faked view] +AS +BEGIN + EXEC ('CREATE VIEW UndoTestDoublesTests.aSimpleView AS SELECT NULL X;'); + DECLARE @OriginalObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleView'); + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleView'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleView'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a faked function] +AS +BEGIN + EXEC ('CREATE FUNCTION UndoTestDoublesTests.aSimpleITVF() RETURNS TABLE AS RETURN SELECT NULL X;'); + EXEC ('CREATE FUNCTION UndoTestDoublesTests.aSimpleTVF() RETURNS @R TABLE(i INT) AS BEGIN RETURN; END;'); + EXEC ('CREATE FUNCTION UndoTestDoublesTests.aSimpleSVF() RETURNS INT AS BEGIN RETURN NULL; END;'); + EXEC ('CREATE FUNCTION UndoTestDoublesTests.aTempSVF() RETURNS INT AS BEGIN RETURN NULL; END;'); + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.FakeFunction @FunctionName = 'UndoTestDoublesTests.aSimpleITVF', @FakeDataSource = '(VALUES(NULL))X(X)'; + EXEC tSQLt.FakeFunction @FunctionName = 'UndoTestDoublesTests.aSimpleTVF', @FakeDataSource = '(VALUES(NULL))X(X)'; + EXEC tSQLt.FakeFunction @FunctionName = 'UndoTestDoublesTests.aSimpleSVF', @FakeFunctionName = 'UndoTestDoublesTests.aTempSVF'; + + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test objects renamed by RemoveObject are restored if there is no other object of the same original name] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test objects renamed by RemoveObject are restored and conflicting objects with IsTempObject property are deleted] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; + EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleTable AS PRINT ''Who came up with that name?'';'); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.aSimpleTable', @ObjectType = N'PROCEDURE', @NewNameOfOriginalObject = ''; + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test synonyms are restored] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + EXEC ('CREATE SYNONYM UndoTestDoublesTests.aSimpleSynonym FOR UndoTestDoublesTests.aSimpleTable;'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleSynonym'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test doubled synonyms with IsTempObject property are deleted] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + EXEC ('CREATE VIEW UndoTestDoublesTests.aSimpleObject AS SELECT 1 X;'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleObject'; + EXEC ('CREATE SYNONYM UndoTestDoublesTests.aSimpleObject FOR UndoTestDoublesTests.aSimpleTable;'); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName='UndoTestDoublesTests.aSimpleObject', @ObjectType='SYNONYM', @NewNameOfOriginalObject = ''; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test drops only objects that are marked as temporary (IsTempObject = 1)] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[SimpleTable1])', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.UndoTestDoubles; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test does not drop multiple unmarked objects (IsTempObject is not set or is not 1)] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); + + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2'; + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable3'; + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[SimpleTable1], [UndoTestDoublesTests].[SimpleTable2], [UndoTestDoublesTests].[SimpleTable3])', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.UndoTestDoubles; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test does not drop marked object where IsTempObject is not 1] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC sys.sp_addextendedproperty + @name = N'tSQLt.IsTempObject', + @value = 42, + @level0type = N'SCHEMA', @level0name = 'UndoTestDoublesTests', + @level1type = N'TABLE', @level1name = 'SimpleTable1'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[SimpleTable1])', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.UndoTestDoubles; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test objects renamed by RemoveObject are restored and conflicting objects without IsTempObject property are deleted if @Force=1] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; + EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleTable AS PRINT ''Who came up with that name?'';'); + + EXEC tSQLt.UndoTestDoubles @Force=1; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test warning message is printed for objects without IsTempObject property that are deleted if @Force=1] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; + EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleTable AS PRINT ''Who came up with that name?'';'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; + EXEC sys.sp_dropextendedproperty + @name = N'tSQLt.IsTempObject', + @level0type = N'SCHEMA', @level0name = 'tSQLt', + @level1type = N'TABLE', @level1name = 'Private_Print_SpyProcedureLog'; + + EXEC tSQLt.UndoTestDoubles @Force=1; + + SELECT Message INTO #Actual FROM tSQLt.Private_Print_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('WARNING: @Force has been set to 1. Overriding the following error(s):Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[aSimpleTable])'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/* --------------------------------------------------------------------------------------------- */ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test objects that are replaced multiple times by objects not marked as IsTempObject are restored if @Force=1] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; + EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleTable AS PRINT ''Replacement 1'';'); + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; + EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleTable AS PRINT ''Replacement 2'';'); + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; + EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleTable AS PRINT ''Replacement 3'';'); + + EXEC tSQLt.UndoTestDoubles @Force=1; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/* --------------------------------------------------------------------------------------------- */ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test non-testdouble object with @IsTempObjects=1 is also dropped] +AS +BEGIN + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1', @ObjectType=N'TABLE', @NewNameOfOriginalObject=NULL; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/* --------------------------------------------------------------------------------------------- */ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test multiple non-testdouble objects with @IsTempObjects=1 are all dropped] +AS +BEGIN + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); + + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable3', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test recovers table that was first faked and then removed] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.SimpleTable1'; + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test throws useful error if two objects with @IsTempObject<>1 need to be renamed to the same name] +AS +BEGIN + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'Attempting to rename two or more objects to the same name. Use @Force = 1 to override, only first object of each rename survives. ({[[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%]}-->[[]UndoTestDoublesTests].[[]SimpleTable1])', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.UndoTestDoubles; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test throws useful error if there are multiple object tuples with @IsTempObject<>1 needing to be renamed to the same name] +AS +BEGIN + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2'; + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2'; + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2'; + CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable3'; + CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable3'; + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'Attempting to rename two or more objects to the same name. Use @Force = 1 to override, only first object of each rename survives. ({[[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%]}-->[[]UndoTestDoublesTests].[[]SimpleTable1]; {[[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%]}-->[[]UndoTestDoublesTests].[[]SimpleTable2]; {[[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%]}-->[[]UndoTestDoublesTests].[[]SimpleTable3])', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.UndoTestDoubles; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test if two objects with @IsTempObject<>1 need to be renamed to the same name and @Force=1 the oldest survives] +AS +BEGIN + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + INSERT INTO UndoTestDoublesTests.SimpleTable1 VALUES (6); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + INSERT INTO UndoTestDoublesTests.SimpleTable1 VALUES (4); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + + EXEC tSQLt.UndoTestDoubles @Force=1; + + SELECT i INTO #Actual FROM UndoTestDoublesTests.SimpleTable1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(6); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test two objects with @IsTempObject<>1 and the same name but in different schemas are restored] +AS +BEGIN + EXEC('CREATE SCHEMA RandomSchema1;'); + CREATE TABLE RandomSchema1.SimpleTable1 (i INT); + INSERT INTO RandomSchema1.SimpleTable1 VALUES (4); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable1'; + + EXEC('CREATE SCHEMA RandomSchema2;'); + CREATE TABLE RandomSchema2.SimpleTable1 (i INT); + INSERT INTO RandomSchema2.SimpleTable1 VALUES (6); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema2.SimpleTable1'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT * INTO #Actual + FROM( + SELECT 'RandomSchema1' [schema_name], i FROM RandomSchema1.SimpleTable1 + UNION + SELECT 'RandomSchema2' [schema_name], i FROM RandomSchema2.SimpleTable1 + ) A + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('RandomSchema1', 4),('RandomSchema2',6); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test throws useful error if there are multiple object tuples in separate schemata but all with the same name] +AS +BEGIN + + EXEC('CREATE SCHEMA RandomSchema1;'); + EXEC('CREATE SCHEMA RandomSchema2;'); + + DECLARE @CurrentTableObjectId INT + CREATE TABLE RandomSchema1.SimpleTable1 (i INT); + SET @CurrentTableObjectId = OBJECT_ID('RandomSchema1.SimpleTable1'); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable1'; + DECLARE @Name1A NVARCHAR(MAX) = OBJECT_NAME(@CurrentTableObjectId); + + CREATE TABLE RandomSchema1.SimpleTable1 (i INT); + SET @CurrentTableObjectId = OBJECT_ID('RandomSchema1.SimpleTable1'); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable1'; + DECLARE @Name1B NVARCHAR(MAX) = OBJECT_NAME(@CurrentTableObjectId); + + CREATE TABLE RandomSchema2.SimpleTable1 (i INT); + SET @CurrentTableObjectId = OBJECT_ID('RandomSchema2.SimpleTable1'); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema2.SimpleTable1'; + DECLARE @Name2A NVARCHAR(MAX) = OBJECT_NAME(@CurrentTableObjectId); + + CREATE TABLE RandomSchema2.SimpleTable1 (i INT); + SET @CurrentTableObjectId = OBJECT_ID('RandomSchema2.SimpleTable1'); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema2.SimpleTable1'; + DECLARE @Name2B NVARCHAR(MAX) = OBJECT_NAME(@CurrentTableObjectId); + + DECLARE @ExpectedMessage NVARCHAR(MAX) = 'Attempting to rename two or more objects to the same name. Use @Force = 1 to override, only first object of each rename survives. ({['+@Name1A+'], ['+@Name1B+']}-->[RandomSchema1].[SimpleTable1]; {['+@Name2A+'], ['+@Name2B+']}-->[RandomSchema2].[SimpleTable1])' + EXEC tSQLt.ExpectException @ExpectedMessage = @ExpectedMessage, @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.UndoTestDoubles; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test can handle the kitchen sink] +AS +BEGIN + EXEC('CREATE SCHEMA RandomSchema1;'); + EXEC('CREATE SCHEMA RandomSchema2;'); + CREATE TABLE RandomSchema1.SimpleTable1 (i INT); + CREATE TABLE RandomSchema1.SimpleTable2 (i INT CONSTRAINT [s1t2pk] PRIMARY KEY); + CREATE TABLE RandomSchema1.SimpleTable3 (i INT); + + CREATE TABLE RandomSchema2.SimpleTable1 (i INT); + CREATE TABLE RandomSchema2.SimpleTable2 (i INT); + CREATE TABLE RandomSchema2.SimpleTable3 (i INT); + + EXEC('CREATE PROCEDURE RandomSchema1.Proc1 AS RETURN;') + EXEC('CREATE PROCEDURE RandomSchema1.Proc2 AS RETURN;') + EXEC('CREATE PROCEDURE RandomSchema1.Proc3 AS RETURN;') + + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable1'; + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable3'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable2'; + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable1'; + EXEC tSQLt.ApplyConstraint @TableName = 'RandomSchema1.SimpleTable2', @ConstraintName = 's1t2pk'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema2.SimpleTable3'; + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.Proc3'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable2'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc2'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema2.SimpleTable1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema2.SimpleTable3'; + CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable3', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + + EXEC tSQLt.ApplyConstraint @TableName = 'RandomSchema1.SimpleTable2', @ConstraintName = 's1t2pk'; + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable2'; + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable1'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + diff --git a/Tests/UninstallTests.class.sql b/Tests/UninstallTests.class.sql new file mode 100644 index 000000000..7af78b590 --- /dev/null +++ b/Tests/UninstallTests.class.sql @@ -0,0 +1,179 @@ +EXEC tSQLt.NewTestClass @ClassName = 'UninstallTests'; +GO +CREATE PROCEDURE UninstallTests.[test Uninstall removes schema tSQLt] +AS +BEGIN + DECLARE @id INT; + BEGIN TRAN; + DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; + SAVE TRAN @TranName; + + EXEC tSQLt.Uninstall; + SET @id = SCHEMA_ID('tSQLt'); + + ROLLBACK TRAN @TranName; + COMMIT TRAN; + + IF @id IS NOT NULL + BEGIN + EXEC tSQLt.Fail 'tSQLt schema not removed'; + END; +END; +GO + +CREATE PROCEDURE UninstallTests.[test Uninstall removes data type tSQLt.Private] +AS +BEGIN + DECLARE @id INT; + BEGIN TRAN; + DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; + SAVE TRAN @TranName; + + EXEC tSQLt.Uninstall; + SET @id = TYPE_ID('tSQLt.Private'); + + ROLLBACK TRAN @TranName; + COMMIT TRAN; + + IF @id IS NOT NULL + BEGIN + EXEC tSQLt.Fail 'tSQLt.Private data type not removed'; + END; +END; +GO + +CREATE PROCEDURE UninstallTests.[test Uninstall removes the tSQLt Assembly] +AS +BEGIN + DECLARE @id INT; + BEGIN TRAN; + DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; + SAVE TRAN @TranName; + + EXEC tSQLt.Uninstall; + + SET @id = (SELECT assembly_id FROM sys.assemblies WHERE name = 'tSQLtCLR'); + + ROLLBACK TRAN @TranName; + COMMIT TRAN; + + IF @id IS NOT NULL + BEGIN + EXEC tSQLt.Fail 'tSQLtCLR assembly not removed'; + END; +END; +GO + +CREATE PROCEDURE UninstallTests.[test Uninstall removes 'tSQLt.TestClass' user] +AS +BEGIN + DECLARE @id INT; + BEGIN TRAN; + DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; + SAVE TRAN @TranName; + + EXEC tSQLt.Uninstall; + SET @id = USER_ID('tSQLt.TestClass'); + + ROLLBACK TRAN @TranName; + COMMIT TRAN; + + IF @id IS NOT NULL + BEGIN + EXEC tSQLt.Fail 'tSQLt.TestClass user not removed'; + END; +END; +GO +CREATE PROCEDURE UninstallTests.[test Uninstall does not fail if the 'tSQLt.TestClass' user is missing] +AS +BEGIN + DECLARE @id INT; + DECLARE @ErrorMsg NVARCHAR(MAX) = NULL; + BEGIN TRAN; + DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; + SAVE TRAN @TranName; + + DROP USER [tSQLt.TestClass]; + + BEGIN TRY + EXEC tSQLt.Uninstall; + END TRY + BEGIN CATCH + SET @ErrorMsg = ERROR_MESSAGE(); + END CATCH; + SET @id = USER_ID('tSQLt.TestClass'); + + ROLLBACK TRAN @TranName; + COMMIT TRAN; + + EXEC tSQLt.AssertEqualsString @Expected = NULL, + @Actual = @ErrorMsg, + @Message = N'Expected no error to be raised, but got: ' +END; +GO +CREATE PROCEDURE UninstallTests.[test Uninstall fails if the 'tSQLt.TestClass' user is referenced] +AS +BEGIN + DECLARE @id INT; + DECLARE @ErrorNumber INT = -1; + DECLARE @ErrorMessage NVARCHAR(MAX) = NULL; + BEGIN TRAN; + DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; + SAVE TRAN @TranName; + + EXEC ('CREATE SCHEMA aTestSchema1 AUTHORIZATION [tSQLt.TestClass];'); + + BEGIN TRY + EXEC tSQLt.Uninstall; + END TRY + BEGIN CATCH + SET @ErrorNumber = ERROR_NUMBER(); + SET @ErrorMessage = ERROR_MESSAGE(); + END CATCH; + SET @id = USER_ID('tSQLt.TestClass'); + + ROLLBACK TRAN @TranName; + COMMIT TRAN; + + DECLARE @FailureMessage NVARCHAR(MAX) = N'Expected a database-principal-cannot-be-dropped message, but was: ' + ISNULL(@ErrorMessage,'No error.') + ' | For ERROR_NUMBER:'; + EXEC tSQLt.AssertEquals @Expected = 15138, + @Actual = @ErrorNumber, + @Message = @FailureMessage; +END; +GO +CREATE PROCEDURE UninstallTests.[test Uninstall does not fail if the tSQLtCLR assembly is missing] +AS +BEGIN + DECLARE @id INT; + DECLARE @ErrorMsg NVARCHAR(MAX) = NULL; + BEGIN TRAN; + DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; + SAVE TRAN @TranName; + + DROP PROCEDURE tSQLt.ResultSetFilter; + DROP PROCEDURE tSQLt.AssertResultSetsHaveSameMetaData; + DROP PROCEDURE tSQLt.NewConnection; + DROP PROCEDURE tSQLt.CaptureOutput; + DROP PROCEDURE tSQLt.SuppressOutput; + DROP FUNCTION tSQLt.Private_GetAnnotationList; + DROP TYPE tSQLt.[Private]; + + DROP ASSEMBLY tSQLtCLR; + + BEGIN TRY + EXEC tSQLt.Uninstall; + END TRY + BEGIN CATCH + SET @ErrorMsg = ERROR_MESSAGE(); + END CATCH; + SET @id = USER_ID('tSQLt.TestClass'); + + ROLLBACK TRAN @TranName; + COMMIT TRAN; + + EXEC tSQLt.AssertEqualsString @Expected = NULL, + @Actual = @ErrorMsg, + @Message = N'Expected no error to be raised, but got: ' +END; +GO + diff --git a/Tests/_ExploratoryTests.class.sql b/Tests/_ExploratoryTests.class.sql new file mode 100644 index 000000000..633043507 --- /dev/null +++ b/Tests/_ExploratoryTests.class.sql @@ -0,0 +1,366 @@ +EXEC tSQLt.NewTestClass '_ExploratoryTests'; +GO +CREATE PROCEDURE [_ExploratoryTests].[test NULL can be CAST into any datatype] +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX) = + ( + SELECT ',CAST(NULL AS '+QUOTENAME(SCHEMA_NAME(schema_id))+'.'+QUOTENAME(name)+')['+CAST(user_type_id AS NVARCHAR(MAX))+']' + FROM sys.types + WHERE is_user_defined = 0 + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + SET @cmd = STUFF(@cmd,1,1,''); + SET @cmd = 'SELECT TOP(0) '+@cmd+' INTO [_ExploratoryTests].DataTypeTestTable;' + EXEC(@cmd); + SELECT * + INTO #Actual + FROM sys.columns + WHERE object_id = OBJECT_ID('[_ExploratoryTests].DataTypeTestTable') + AND name <> user_type_id + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO + +CREATE PROCEDURE [_ExploratoryTests].[test MSSQL preserves COLLATION when using SELECT INTO] +AS +BEGIN + SELECT + 'Hello World!' COLLATE SQL_Polish_CP1250_CI_AS c1, + 'Hello World!' COLLATE SQL_Latin1_General_CP437_BIN c2, + 'Hello World!' COLLATE Albanian_BIN2 c3 + INTO [_ExploratoryTests].Table1 + + SELECT * INTO [_ExploratoryTests].Table2 FROM [_ExploratoryTests].Table1 + + EXEC tSQLt.AssertEqualsTableSchema @Expected = '[_ExploratoryTests].Table1', @Actual = '[_ExploratoryTests].Table2'; +END; +GO +CREATE PROCEDURE [_ExploratoryTests].[test MSSQL creates INTO table before processing] +AS +BEGIN + SELECT 1 X INTO #Test; + DECLARE @NotExpected INT = OBJECT_ID('tempdb..#Test'); + DECLARE @Actual INT; + + EXEC sys.sp_executesql N' + SELECT * INTO #Test FROM tempdb.sys.objects C WHERE C.object_id = OBJECT_ID(''tempdb..#Test''); + EXEC sys.sp_executesql N''SELECT @Actual = object_id FROM #Test;'',N''@Actual INT OUTPUT'',@Actual OUT;', + N'@Actual INT OUTPUT', + @Actual OUT; + + EXEC tSQLt.AssertNotEquals @Expected = @NotExpected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE [_ExploratoryTests].[test MSSQL creates INTO table after compiling] +AS +BEGIN + SELECT 1 X INTO #Test; + DECLARE @NotExpected INT = OBJECT_ID('tempdb..#Test'); + DECLARE @Actual INT; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Invalid column name ''object_id''.'; + EXEC sys.sp_executesql N' + SELECT * INTO #Test FROM tempdb.sys.objects C WHERE C.object_id = OBJECT_ID(''tempdb..#Test''); + SELECT @Actual = object_id FROM #Test;', + N'@Actual INT OUTPUT', + @Actual OUT; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test FOR XML returns NULL for empty result set] +AS +BEGIN + SELECT ((SELECT 1 WHERE 1 = 0 FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)')) [FOR XML FROM EMPTY] INTO #Actual; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(NULL); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR_STATUS indicates whether a cursor variable is set (and other things)] +AS +BEGIN + CREATE TABLE #Actual (Situation NVARCHAR(MAX), [Cursor Status] INT, [Fetch Status] INT); + EXEC('INSERT INTO #Actual SELECT ''Variable not defined'', CURSOR_STATUS(''variable'',''@ACursor''),NULL;'); + DECLARE @ACursor CURSOR; + INSERT INTO #Actual SELECT 'Variable defined (DECLARE)', CURSOR_STATUS('variable','@ACursor'), NULL; + SET @ACursor = CURSOR FOR SELECT 1; + INSERT INTO #Actual SELECT 'Variable allocated (SET)', CURSOR_STATUS('variable','@ACursor'), NULL; + OPEN @ACursor; + INSERT INTO #Actual SELECT 'Cursor opened', CURSOR_STATUS('variable','@ACursor'), NULL; + DECLARE @IgnoreThis INT; + FETCH NEXT FROM @ACursor INTO @IgnoreThis; + INSERT INTO #Actual SELECT 'Cursor after fetch', CURSOR_STATUS('variable','@ACursor'), @@FETCH_STATUS; + FETCH NEXT FROM @ACursor INTO @IgnoreThis; + INSERT INTO #Actual SELECT 'Cursor after final fetch', CURSOR_STATUS('variable','@ACursor'), @@FETCH_STATUS; + CLOSE @ACursor; + INSERT INTO #Actual SELECT 'Cursor closed', CURSOR_STATUS('variable','@ACursor'), NULL; + DEALLOCATE @ACursor; + INSERT INTO #Actual SELECT 'Cursor deallocated', CURSOR_STATUS('variable','@ACursor'), NULL; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES + ('Variable not defined',-3,NULL), + ('Variable defined (DECLARE)',-2,NULL), + ('Variable allocated (SET)',-1,NULL), + ('Cursor opened',1,NULL), + ('Cursor after fetch',1,0), + ('Cursor after final fetch',1,-1), + ('Cursor closed',-1,NULL), + ('Cursor deallocated',-2,NULL); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR will not be passed out through OUTPUT @parameter if it has not been opened (CURSOR_STATUS = -1)] +AS +BEGIN + DECLARE @CursorProc NVARCHAR(MAX) = '[_ExploratoryTests].CursorProc'; + EXEC(' + CREATE PROCEDURE '+@CursorProc+' + @CursorParameter CURSOR VARYING OUTPUT + AS + BEGIN + SET @CursorParameter = CURSOR FOR SELECT 42; + END; + '); + + DECLARE @CursorVariable CURSOR; + + EXEC @CursorProc @CursorParameter = @CursorVariable OUTPUT; + + CREATE TABLE #Actual ([Cursor Status] INT); + INSERT INTO #Actual SELECT CURSOR_STATUS('variable','@CursorVariable'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(-2); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR can be passed out through OUTPUT @parameter if it has been opened (CURSOR_STATUS = 1)] +AS +BEGIN + DECLARE @CursorProc NVARCHAR(MAX) = '[_ExploratoryTests].CursorProc'; + EXEC(' + CREATE PROCEDURE '+@CursorProc+' + @CursorParameter CURSOR VARYING OUTPUT + AS + BEGIN + SET @CursorParameter = CURSOR FOR SELECT 42; + OPEN @CursorParameter; + END; + '); + + DECLARE @IntValue INT = NULL; + DECLARE @CursorVariable CURSOR; + + EXEC @CursorProc @CursorParameter = @CursorVariable OUTPUT; + + FETCH NEXT FROM @CursorVariable INTO @IntValue; + CLOSE @CursorVariable; + DEALLOCATE @CursorVariable; + + EXEC tSQLt.AssertEquals @Expected = 42, @Actual = @IntValue; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR cannot be passed to a proc with OUTPUT specified in the call if it has been allocated (SET, CURSOR_STATUS = -1)] +AS +BEGIN + DECLARE @CursorProc NVARCHAR(MAX) = '[_ExploratoryTests].CursorProc'; + EXEC(' + CREATE PROCEDURE '+@CursorProc+' + @CursorParameter CURSOR VARYING OUTPUT + AS + BEGIN + RETURN; + END; + '); + + DECLARE @IntValue INT = NULL; + DECLARE @CursorVariable CURSOR; + SET @CursorVariable = CURSOR FOR SELECT 13; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'The variable ''@CursorVariable'' cannot be used as a parameter because a CURSOR OUTPUT parameter must not have a cursor allocated to it before execution of the procedure.'; + EXEC @CursorProc @CursorParameter = @CursorVariable OUTPUT; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR can be passed to a proc without OUTPUT specified in the call even if it has been allocated (SET, CURSOR_STATUS = -1)] +AS +BEGIN + DECLARE @CursorProc NVARCHAR(MAX) = '[_ExploratoryTests].CursorProc'; + EXEC(' + CREATE PROCEDURE '+@CursorProc+' + @CursorParameter CURSOR VARYING OUTPUT + AS + BEGIN + RETURN; + END; + '); + + DECLARE @IntValue INT = NULL; + DECLARE @CursorVariable CURSOR; + SET @CursorVariable = CURSOR FOR SELECT 13; + + EXEC tSQLt.ExpectNoException; + EXEC @CursorProc @CursorParameter = @CursorVariable; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR gets deallocated when its variable goes out of scope?] +AS +BEGIN + SELECT IDENTITY(INT,1,1) AS ID, name, is_open, fetch_status + INTO #Actual FROM (SELECT NULL)ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = '@ACursor'; + + EXEC(' + DECLARE @ACursor CURSOR; + SET @ACursor = CURSOR FOR SELECT 42; + INSERT INTO #Actual SELECT name, is_open, fetch_status + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''@ACursor''; + OPEN @ACursor; + INSERT INTO #Actual SELECT name, is_open, fetch_status + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''@ACursor''; + DECLARE @I INT; FETCH NEXT FROM @ACursor INTO @I; + INSERT INTO #Actual SELECT name, is_open, fetch_status + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''@ACursor''; + '); + + INSERT INTO #Actual SELECT name, is_open, fetch_status + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = '@ACursor'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES + (1,NULL,NULL,NULL), + (2,'@ACursor','false', -9), + (3,'@ACursor','true', -9), + (4,'@ACursor','true', 0), + (5,NULL,NULL,NULL); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test LOCAL CURSOR (not a variable) gets deallocated when it goes out of scope?] +AS +BEGIN + SELECT IDENTITY(INT,1,1) AS ID, name, is_open, fetch_status, CURSOR_STATUS('local','ACursor') AS [cursor status] + INTO #Actual FROM (SELECT NULL)ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = 'ACursor'; + + EXEC(' + DECLARE ACursor CURSOR LOCAL FOR SELECT 42; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''local'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + OPEN ACursor; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''local'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + DECLARE @I INT; FETCH NEXT FROM ACursor INTO @I; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''local'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + '); + + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS('local','ACursor') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = 'ACursor'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES + (1,NULL,NULL,NULL, -3), + (2,'ACursor','false', -9, -1), + (3,'ACursor','true', -9, 1), + (4,'ACursor','true', 0, 1), + (5,NULL,NULL,NULL, -3); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test GLOBAL CURSOR (not a variable) gets deallocated when it goes out of scope?] +AS +BEGIN + SELECT IDENTITY(INT,1,1) AS ID, name, is_open, fetch_status, CURSOR_STATUS('global','ACursor') AS [cursor status] + INTO #Actual FROM (SELECT NULL)ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = 'ACursor'; + + EXEC(' + DECLARE ACursor CURSOR /*GLOBAL*/ FOR SELECT 42; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''global'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + OPEN ACursor; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''global'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + DECLARE @I INT; FETCH NEXT FROM ACursor INTO @I; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''global'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + '); + + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS('global','ACursor') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = 'ACursor'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES + (1,NULL,NULL,NULL, -3), + (2,'ACursor','false', -9, -1), + (3,'ACursor','true', -9, 1), + (4,'ACursor','true', 0, 1), + (5,'ACursor','true', 0, 1); + BEGIN TRY CLOSE ACursor; END TRY BEGIN CATCH END CATCH; + BEGIN TRY DEALLOCATE ACursor; END TRY BEGIN CATCH END CATCH; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test sp_addextendedproperty can handle odd values] +AS +BEGIN + CREATE TABLE [_ExploratoryTests].ATable(I INT); + + EXEC sys.sp_addextendedproperty + @name = N'ATestProperty', + @value = 'a string.with''special chars', + @level0type = N'SCHEMA', @level0name = '_ExploratoryTests', + @level1type = 'TABLE', @level1name = 'ATable'; + + SELECT * FROM sys.extended_properties AS EP WHERE EP.major_id = OBJECT_ID('[_ExploratoryTests].ATable'); + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +--CREATE PROCEDURE [_ExploratoryTests].[test TBD] +--AS +--BEGIN +-- EXEC tSQLt.Fail 'TemplateTest'; +--END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +--EXEC tSQLt.Run [_ExploratoryTests] \ No newline at end of file diff --git a/Tests/tSQLt_test.class.sql b/Tests/tSQLt_test.class.sql index f009c5fc5..5107cdb49 100644 --- a/Tests/tSQLt_test.class.sql +++ b/Tests/tSQLt_test.class.sql @@ -95,33 +95,66 @@ GO CREATE PROC tSQLt_test.test_Run_handles_uncommitable_transaction AS +BEGIN + DECLARE @TranName sysname; + + SELECT TOP(1) @TranName = TranName FROM tSQLt.TestResult WHERE Class = 'tSQLt_test' AND TestCase = 'test_Run_handles_uncommitable_transaction' ORDER BY Id DESC; + EXEC ('CREATE PROC tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063 AS BEGIN CREATE TABLE t1 (i int); CREATE TABLE t1 (i int); END;'); + BEGIN TRY + EXEC tSQLt.Run 'tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063'; + END TRY + BEGIN CATCH + --Left intentionally empty + END CATCH; + + SELECT Class, TestCase, Result, Msg + INTO #Actual + FROM tSQLt.TestResult + WHERE TestCase = 'testUncommitable00A1030051764AE7A946E827159E7063'; + + SELECT TOP(0) A.Class,A.TestCase,A.Result,A.Msg AS [%Msg] INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + SELECT 'tSQLt_test' Class, 'testUncommitable00A1030051764AE7A946E827159E7063' TestCase, 'Error' Result, '%There is already an object named ''t1'' in the database.%The current transaction cannot be committed and cannot be rolled back to a savepoint.%' [%Msg]; + + SELECT * INTO #Compare + FROM( + SELECT '>' _R_,* FROM #Actual AS A WHERE NOT EXISTS(SELECT 1 FROM #Expected E WHERE A.Class = E.Class AND A.TestCase = E.TestCase AND A.Result = E.Result AND A.Msg LIKE E.[%Msg]) + UNION ALL + SELECT '<' _R_,* FROM #Expected AS E WHERE NOT EXISTS(SELECT 1 FROM #Actual A WHERE A.Class = E.Class AND A.TestCase = E.TestCase AND A.Result = E.Result AND A.Msg LIKE E.[%Msg]) + )X; + IF(@@ROWCOUNT>0) + BEGIN + INSERT INTO #Compare + SELECT '=' _R_,* FROM (SELECT * FROM #Actual INTERSECT SELECT * FROM #Expected)X; + END; + EXEC tSQLt.AssertEmptyTable '#Compare'; + + DELETE FROM tSQLt.TestResult + WHERE TestCase = 'testUncommitable00A1030051764AE7A946E827159E7063'; + BEGIN TRAN; + SAVE TRAN @TranName; +END; +GO + + +CREATE PROC tSQLt_test.test_Run_ROLLsBACK_uncommitable_transaction +AS BEGIN DECLARE @TranName sysname; - SELECT TOP(1) @TranName = TranName FROM tSQLt.TestResult WHERE Class = 'tSQLt_test' AND TestCase = 'test_Run_handles_uncommitable_transaction' ORDER BY Id DESC; - EXEC ('CREATE PROC testUncommitable AS BEGIN CREATE TABLE t1 (i int); CREATE TABLE t1 (i int); END;'); + SELECT TOP(1) @TranName = TranName FROM tSQLt.TestResult WHERE Class = 'tSQLt_test' AND TestCase = 'test_Run_ROLLsBACK_uncommitable_transaction' ORDER BY Id DESC; + + EXEC ('CREATE PROC tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063 AS BEGIN CREATE TABLE t1 (i int); CREATE TABLE t1 (i int); END;'); BEGIN TRY - EXEC tSQLt.Run 'testUncommitable'; + EXEC tSQLt.Run 'tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063'; END TRY BEGIN CATCH - IF NOT EXISTS(SELECT 1 - FROM tSQLt.TestResult - WHERE TestCase = 'testUncommitable' - AND Result = 'Error' - AND Msg LIKE '%There is already an object named ''t1'' in the database.[[]%]{testUncommitable,1}%' - AND Msg LIKE '%The current transaction cannot be committed and cannot be rolled back to a savepoint.%' - ) - BEGIN - EXEC tSQLt.Fail 'tSQLt.Run ''testUncommitable'' did not error correctly'; - END; IF(@@TRANCOUNT > 0) BEGIN - EXEC tSQLt.Fail 'tSQLt.Run ''testUncommitable'' did not rollback the transactions'; + EXEC tSQLt.Fail 'tSQLt.Run ''tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063'' did not rollback the transactions'; END DELETE FROM tSQLt.TestResult - WHERE TestCase = 'testUncommitable' - AND Result = 'Error' - AND Msg LIKE '%There is already an object named ''t1'' in the database.[[]%]{testUncommitable,1}%' - AND Msg LIKE '%The current transaction cannot be committed and cannot be rolled back to a savepoint.%' + WHERE TestCase = 'testUncommitable00A1030051764AE7A946E827159E7063' BEGIN TRAN SAVE TRAN @TranName END CATCH @@ -1307,7 +1340,7 @@ BEGIN SELECT '{' + Msg + '}' AS BracedMsg INTO #actual - FROM tSQLt.TestMessage; + FROM #TestMessage; SELECT TOP(0) * INTO #expected @@ -1330,7 +1363,7 @@ BEGIN SELECT '{' + Msg + '}' AS BracedMsg INTO #actual - FROM tSQLt.TestMessage; + FROM #TestMessage; SELECT TOP(0) * INTO #expected @@ -1342,85 +1375,6 @@ BEGIN END; GO -CREATE PROCEDURE tSQLt_test.[test tSQLt.TestClasses returns no test classes when there are no test classes] -AS -BEGIN - EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; - - SELECT * - INTO #Actual - FROM tSQLt.TestClasses; - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; -END; -GO - -CREATE PROCEDURE tSQLt_test.[test tSQLt.TestClasses returns single test class] -AS -BEGIN - EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; - EXEC tSQLt.NewTestClass 'tSQLt_test_dummy_A'; - - SELECT Name - INTO #Actual - FROM tSQLt.TestClasses; - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - INSERT INTO #Expected(Name) VALUES ('tSQLt_test_dummy_A'); - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; -END; -GO - -CREATE PROCEDURE tSQLt_test.[test tSQLt.TestClasses returns multiple test classes] -AS -BEGIN - EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; - EXEC tSQLt.NewTestClass 'tSQLt_test_dummy_A'; - EXEC tSQLt.NewTestClass 'tSQLt_test_dummy_B'; - - SELECT Name - INTO #Actual - FROM tSQLt.TestClasses; - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - INSERT INTO #Expected(Name) VALUES ('tSQLt_test_dummy_A'); - INSERT INTO #Expected(Name) VALUES ('tSQLt_test_dummy_B'); - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; -END; -GO - -CREATE PROCEDURE tSQLt_test.[test tSQLt.TestClasses returns other important columns] -AS -BEGIN - EXEC tSQLt_testutil.RemoveTestClassPropertyFromAllExistingClasses; - EXEC tSQLt.NewTestClass 'tSQLt_test_dummy_A'; - - SELECT Name,SchemaId - INTO #Actual - FROM tSQLt.TestClasses; - - SELECT TOP(0) * - INTO #Expected - FROM #Actual; - - INSERT INTO #Expected(Name, SchemaId) VALUES ('tSQLt_test_dummy_A',SCHEMA_ID('tSQLt_test_dummy_A')); - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; -END; -GO - CREATE PROCEDURE tSQLt_test.[test tSQLt.Tests returns no tests when there are no test classes] AS BEGIN @@ -1606,69 +1560,7 @@ BEGIN END; GO -CREATE PROCEDURE tSQLt_test.[test Uninstall removes schema tSQLt] -AS -BEGIN - DECLARE @id INT; - BEGIN TRAN; - DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; - SAVE TRAN @TranName; - - EXEC tSQLt.Uninstall; - SET @id = SCHEMA_ID('tSQLt'); - - ROLLBACK TRAN @TranName; - COMMIT TRAN; - - IF @id IS NOT NULL - BEGIN - EXEC tSQLt.Fail 'tSQLt schema not removed'; - END; -END; -GO - -CREATE PROCEDURE tSQLt_test.[test Uninstall removes data type tSQLt.Private] -AS -BEGIN - DECLARE @id INT; - BEGIN TRAN; - DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; - SAVE TRAN @TranName; - EXEC tSQLt.Uninstall; - SET @id = TYPE_ID('tSQLt.Private'); - - ROLLBACK TRAN @TranName; - COMMIT TRAN; - - IF @id IS NOT NULL - BEGIN - EXEC tSQLt.Fail 'tSQLt.Private data type not removed'; - END; -END; -GO - -CREATE PROCEDURE tSQLt_test.[test Uninstall removes the tSQLt Assembly] -AS -BEGIN - DECLARE @id INT; - BEGIN TRAN; - DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; - SAVE TRAN @TranName; - - EXEC tSQLt.Uninstall; - - SET @id = (SELECT assembly_id FROM sys.assemblies WHERE name = 'tSQLtCLR'); - - ROLLBACK TRAN @TranName; - COMMIT TRAN; - - IF @id IS NOT NULL - BEGIN - EXEC tSQLt.Fail 'tSQLtCLR assembly not removed'; - END; -END; -GO --ROLLBACK ---tSQLt_test \ No newline at end of file +--tSQLt_test diff --git a/Tests/tSQLtclr_test.class.sql b/Tests/tSQLtclr_test.class.sql index 37fb16300..5a3ce3b9e 100644 --- a/Tests/tSQLtclr_test.class.sql +++ b/Tests/tSQLtclr_test.class.sql @@ -184,4 +184,14 @@ BEGIN END; END; GO + +CREATE PROC tSQLtclr_test.[test name returned by tSQLt.Private::CreateUniqueObjectName() should not require quoting] +AS +BEGIN + DECLARE @NewName NVARCHAR(MAX) = tSQLt.Private::CreateUniqueObjectName(); + + EXEC tSQLt.ExpectNoException; + EXEC('CREATE TABLE tSQLtclr_test.'+@NewName+'(I INT);'); +END; +GO --ROLLBACK diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 000000000..61017d2ec --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +docs.tsqlt.org \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..abec8fe39 --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +WELCOME TO TSQLT, THE OPEN SOURCE DATABASE UNIT TESTING FRAMEWORK FOR SQL SERVER \ No newline at end of file diff --git a/tSQLt.code-workspace b/tSQLt.code-workspace new file mode 100644 index 000000000..fd39ede6f --- /dev/null +++ b/tSQLt.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "." + } + ], +"settings": { + "powershell.codeFormatting.addWhitespaceAroundPipe": true, +"yaml.schemaStore.enable": false, +"dataworkspace.excludedProjects": [ +], +"dotnet.preferCSharpExtension": true +} +} \ No newline at end of file diff --git a/tSQLt.ssmssln b/tSQLt.ssmssln index 1b41948b2..5a942feb5 100644 --- a/tSQLt.ssmssln +++ b/tSQLt.ssmssln @@ -1,6 +1,8 @@ ο»Ώ -Microsoft Visual Studio Solution File, Format Version 11.00 -# SQL Server Management Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 +# SQL Server Management Studio Solution File, Format Version 18.00 +VisualStudioVersion = 15.0.28307.421 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Source", "Source\Source.ssmssqlproj", "{E5760F57-8934-4790-8420-DA7032D58EB7}" EndProject Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Tests", "Tests\Tests.ssmssqlproj", "{5FFBD87D-F3CF-43CF-BD60-ED175F5B0E92}" @@ -11,46 +13,52 @@ Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Experiments", "Experiments\ EndProject Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Examples", "Examples\Examples.ssmssqlproj", "{71BFF91C-687A-4684-A608-59873DE08BD5}" EndProject -Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Tests.2008", "Tests.2008\Tests.2008.ssmssqlproj", "{B3837996-C4D9-4CA5-88D8-3F5679DC6D06}" -EndProject Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "TestUtil", "TestUtil\TestUtil.ssmssqlproj", "{DD29DD42-530A-43CB-9190-BC4A54C204B4}" EndProject Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Snippets", "Snippets\Snippets.ssmssqlproj", "{DEF31969-367F-4D43-BAF7-4B05758ED516}" EndProject -Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Tests.2012", "Tests.2012\Tests.2012.ssmssqlproj", "{2262182E-53C4-4C18-A0F2-493B36AB5C3C}" -EndProject -Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Tests.2014", "Tests.2014\Tests.2014.ssmssqlproj", "{9CD9A36A-BA81-4D3F-B1E4-29E2BD6E8714}" -EndProject Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Tests.EXTERNAL_ACCESS", "Tests.EXTERNAL_ACCESS\Tests.EXTERNAL_ACCESS.ssmssqlproj", "{E2A27030-663A-4CA8-AAEB-DE8E2D2C1602}" EndProject Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Tests.SA", "Tests.SA\Tests.SA.ssmssqlproj", "{C3E63571-5B2C-4FF0-9FE4-96BB08BEDC7B}" EndProject Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Tests.EXTERNAL_ACCESS_KEY_EXISTS", "Tests.EXTERNAL_ACCESS_KEY_EXISTS\Tests.EXTERNAL_ACCESS_KEY_EXISTS.ssmssqlproj", "{7FC3A926-9834-455A-906E-330FFCF224E3}" EndProject -Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Build_Output", "Build_Output\Build_Output.ssmssqlproj", "{92395F13-BF15-4C42-A876-DC851DE88ACA}" +Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Build_Debug", "Build_Debug\Build_Debug.ssmssqlproj", "{92395F13-BF15-4C42-A876-DC851DE88ACA}" +EndProject +Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "TestUtilTests", "TestUtilTests\TestUtilTests.ssmssqlproj", "{29C01CDC-4748-4118-ACF3-FFFE56A8A989}" +EndProject +Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "CI", "CI\CI.ssmssqlproj", "{244CA5E2-1A9D-4569-A05D-5D0FE85C3F03}" +EndProject +Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "FacadeTests", "FacadeTests\FacadeTests.ssmssqlproj", "{BE51C6D7-1DBA-4845-8512-A13A4BAF2F0D}" +EndProject +Project("{4F2E2C19-372F-40D8-9FA7-9D2138C6997A}") = "Facade", "Facade\Facade.ssmssqlproj", "{2FC4D7CE-389E-4044-AE06-F999E036F6D6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Default|Default = Default|Default EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EBCB5200-E814-4995-8B2A-5F4B4328895F}.Default|Default.ActiveCfg = Default {E5760F57-8934-4790-8420-DA7032D58EB7}.Default|Default.ActiveCfg = Default {5FFBD87D-F3CF-43CF-BD60-ED175F5B0E92}.Default|Default.ActiveCfg = Default {161EC148-F99F-4D07-A8A0-FF7ECB9A1566}.Default|Default.ActiveCfg = Default {923E6ED4-A955-4DCF-AB73-ABB17CB23C44}.Default|Default.ActiveCfg = Default {71BFF91C-687A-4684-A608-59873DE08BD5}.Default|Default.ActiveCfg = Default - {B3837996-C4D9-4CA5-88D8-3F5679DC6D06}.Default|Default.ActiveCfg = Default {DD29DD42-530A-43CB-9190-BC4A54C204B4}.Default|Default.ActiveCfg = Default {DEF31969-367F-4D43-BAF7-4B05758ED516}.Default|Default.ActiveCfg = Default - {2262182E-53C4-4C18-A0F2-493B36AB5C3C}.Default|Default.ActiveCfg = Default - {9CD9A36A-BA81-4D3F-B1E4-29E2BD6E8714}.Default|Default.ActiveCfg = Default {E2A27030-663A-4CA8-AAEB-DE8E2D2C1602}.Default|Default.ActiveCfg = Default {C3E63571-5B2C-4FF0-9FE4-96BB08BEDC7B}.Default|Default.ActiveCfg = Default {7FC3A926-9834-455A-906E-330FFCF224E3}.Default|Default.ActiveCfg = Default - {A48D69DD-2543-46B6-9C85-4AF95F95C33A}.Default|Default.ActiveCfg = Default {92395F13-BF15-4C42-A876-DC851DE88ACA}.Default|Default.ActiveCfg = Default + {29C01CDC-4748-4118-ACF3-FFFE56A8A989}.Default|Default.ActiveCfg = Default + {244CA5E2-1A9D-4569-A05D-5D0FE85C3F03}.Default|Default.ActiveCfg = Default + {BE51C6D7-1DBA-4845-8512-A13A4BAF2F0D}.Default|Default.ActiveCfg = Default + {2FC4D7CE-389E-4044-AE06-F999E036F6D6}.Default|Default.ActiveCfg = Default EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {90FA238E-F2FF-4145-9BD0-3AA0CE7B719F} + EndGlobalSection EndGlobal diff --git a/tSQLtCLR/Build.ps1 b/tSQLtCLR/Build.ps1 new file mode 100644 index 000000000..a0ff38bfc --- /dev/null +++ b/tSQLtCLR/Build.ps1 @@ -0,0 +1,35 @@ +param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $pfxFilePath , + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][securestring] $pfxPassword +) +Push-Location -Path $PSScriptRoot + +# Create a unique temporary directory for this process +$randomNumber = Get-Random -Minimum 10000 -Maximum 99999 +$tempDir = Join-Path -Path "/tmp" -ChildPath $("myapp_${PID}_$randomNumber") +try{ + New-Item -Path $tempDir -ItemType Directory -Force + Invoke-Expression "chmod 700 '$tempDir'" + + $snkFilePath = Join-Path -Path $tempDir -ChildPath "tSQLtOfficialSigningKey.snk" + $pemFilePath = Join-Path -Path $tempDir -ChildPath "tSQLtOfficialSigningKey.pem" + + + # $pfxPasswordCleartext = (ConvertFrom-SecureString $pfxPassword -AsPlainText); + $pfxPasswordCleartext = [System.Runtime.InteropServices.Marshal]::PtrToStringUni([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pfxPassword)); + & openssl pkcs12 -in "$pfxFilePath" -out "$pemFilePath" -nodes -passin pass:$pfxPasswordCleartext + & openssl pkcs12 -in "$pfxFilePath" -noout -info -nodes -passin pass:$pfxPasswordCleartext + Write-Warning("Certificate Thumbprint: " + (Get-PfxCertificate -Filepath "$pfxFilePath" -Password $pfxPassword).Thumbprint.ToString()); + + $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider + $pemContent = Get-Content -Path "$pemFilePath" -Raw + $rsa.ImportFromPem($pemContent.ToCharArray()) + $keyPair = $rsa.ExportCspBlob($true) + [System.IO.File]::WriteAllBytes($snkFilePath, $keyPair) + + & dotnet build /p:tSQLtOfficialSigningKey="$snkFilePath" +} +finally { + try{Remove-Item -Path $tempDir -Recurse -Force}catch{Write-Host "deleting tempdir failed!"} + Pop-Location +} \ No newline at end of file diff --git a/tSQLtCLR/FxCopProject.FxCop b/tSQLtCLR/FxCopProject.FxCop deleted file mode 100644 index a54ec68c7..000000000 --- a/tSQLtCLR/FxCopProject.FxCop +++ /dev/null @@ -1,190 +0,0 @@ -ο»Ώ - - - True - c:\programs\microsoft fxcop 1.36\Xml\FxCopReport.xsl - - - - - - True - True - True - 10 - 1 - - False - - False - 120 - False - - - - - - - - - - - - - - - - - - - - - - - - - - tSQLtCLR - - - - - - - - - - CLR - tSQLtCLR - Clr - - - - - - - - - - Lt - tSQLtCLR - LT - - - - - - - - - - t - tSQLtCLR - T - - - - - - - - - - - - - - - - - - CLR - 'tSQLtCLR.dll' - Clr - - - - - - - - - - Lt - 'tSQLtCLR.dll' - LT - - - - - - - - - - t - 'tSQLtCLR.dll' - T - - - - - - - - - - - - - - - - - - MetaData - 'StoredProcedures.AssertResultSetsHaveSameMetaData(SqlString, SqlString)' - Metadata - - - - - - - - - - - - - - - - - - - - - tSQLt is the product name - tSQLt is the product name - tSQLt is the product name - tSQLtCLR is likely to contain many more types in the future - tSQLt is the product name - tSQLt is the product name - MetaData (with capital D is the .Net convention for the term) - - - - - Consider merging the types defined in '{0}' with another namespace. - - - The compound word '{0}' in member name {1} exists as a discrete term. If your usage is intended to be single word, case it as '{2}' or strip the first token entirely if it represents any sort of Hungarian notation. - - - Correct the casing of '{0}' in assembly name {1} by changing it to '{2}'. - Correct the casing of '{0}' in namespace name '{1}' by changing it to '{2}'. - - - - diff --git a/tSQLtCLR/OfficialSigningKey/CreateBase64KeyFile.ps1 b/tSQLtCLR/OfficialSigningKey/CreateBase64KeyFile.ps1 new file mode 100644 index 000000000..89b325901 --- /dev/null +++ b/tSQLtCLR/OfficialSigningKey/CreateBase64KeyFile.ps1 @@ -0,0 +1 @@ +Set-Content -Path $PSScriptRoot'\tSQLtOfficialSigningKey.b64.txt' -Value ([Convert]::ToBase64String([IO.File]::ReadAllBytes($PSScriptRoot+'\tSQLtOfficialSigningKey.pfx'))) \ No newline at end of file diff --git a/tSQLtCLR/OfficialSigningKey/InstallSigningKey.bat b/tSQLtCLR/OfficialSigningKey/InstallSigningKey.bat new file mode 100644 index 000000000..67a76d25a --- /dev/null +++ b/tSQLtCLR/OfficialSigningKey/InstallSigningKey.bat @@ -0,0 +1,46 @@ +@ECHO OFF +REM This installs the signing key for tSQLt on the local machine. +REM If you do not have access to the original key, create your own and use SN.exe to install it into the tSQLt_OfficialSigningKey container. +REM This is only needed if you want to build tSQLt yourself. +REM + +cd /d "%~dp0" +DEL KeyInfo.txt +DEL KeyInfoC.txt +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO Are key containers computer-specific, or user-specific +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO. +"c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\sn.exe" -m +ECHO. +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO Deleting existing container (might fail) +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO. +"c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\sn.exe" -d tSQLt_OfficialSigningKey +ECHO. +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO Installing Key +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO. +"c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\sn.exe" -i tSQLtOfficialSigningKey.pfx tSQLt_OfficialSigningKey +ECHO. +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO Extracting Key Info +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO. +"c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\sn.exe" -p tSQLtOfficialSigningKey.pfx KeyInfo.txt sha1 +"c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\sn.exe" -pc tSQLt_OfficialSigningKey KeyInfoC.txt sha1 +ECHO. +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO KeyInfo from file +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO. +"c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\sn.exe" -tp KeyInfo.txt +ECHO. +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO KeyInfo from container +ECHO -------------------------------------------------------------------------------------------------------------------- +ECHO. +"c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\sn.exe" -tp KeyInfoC.txt + diff --git a/tSQLtCLR/OfficialSigningKey/tSQLtOfficialSigningKey.pfx b/tSQLtCLR/OfficialSigningKey/tSQLtOfficialSigningKey.pfx new file mode 100644 index 000000000..80c311daf Binary files /dev/null and b/tSQLtCLR/OfficialSigningKey/tSQLtOfficialSigningKey.pfx differ diff --git a/tSQLtCLR/UnsignedEmpty/Properties/AssemblyInfo.cs b/tSQLtCLR/UnsignedEmpty/Properties/XXXX.AssemblyInfo.cs.XXXX similarity index 96% rename from tSQLtCLR/UnsignedEmpty/Properties/AssemblyInfo.cs rename to tSQLtCLR/UnsignedEmpty/Properties/XXXX.AssemblyInfo.cs.XXXX index 772411e0e..dcaa55132 100644 --- a/tSQLtCLR/UnsignedEmpty/Properties/AssemblyInfo.cs +++ b/tSQLtCLR/UnsignedEmpty/Properties/XXXX.AssemblyInfo.cs.XXXX @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("UnsignedEmpty")] -[assembly: AssemblyCopyright("Copyright Β© 2015")] +[assembly: AssemblyCopyright("Copyright Β© 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/tSQLtCLR/UnsignedEmpty/UnsignedEmpty.csproj b/tSQLtCLR/UnsignedEmpty/UnsignedEmpty.csproj index acba563a1..1c174ec89 100644 --- a/tSQLtCLR/UnsignedEmpty/UnsignedEmpty.csproj +++ b/tSQLtCLR/UnsignedEmpty/UnsignedEmpty.csproj @@ -1,52 +1,25 @@ -ο»Ώ - - - - Debug - AnyCPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8} - Library - Properties - UnsignedEmpty - UnsignedEmpty - v3.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - bin\CruiseControl\ - - - bin\CI%28NoSigning%29\ - AnyCPU - MinimumRecommendedRules.ruleset - - - - - - - \ No newline at end of file + + + net40 + true + UnsignedEmpty + An empty (no code) assembly that is not signed + tSQLt Foundation + tSQLt + Copyright Β© sqlity.net 2010 - $(Year) + false + true + Release + + false + false + true + + + + $([System.DateTime]::Now.Year) + 1.0.0.0 + $(AssemblyVersion) + + + diff --git a/tSQLtCLR/UnsignedEmpty/XXXX.UnsignedEmpty.csproj.XXXX b/tSQLtCLR/UnsignedEmpty/XXXX.UnsignedEmpty.csproj.XXXX new file mode 100644 index 000000000..0c9bd76b8 --- /dev/null +++ b/tSQLtCLR/UnsignedEmpty/XXXX.UnsignedEmpty.csproj.XXXX @@ -0,0 +1,55 @@ +ο»Ώ + + + + Debug + AnyCPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8} + Library + Properties + UnsignedEmpty + UnsignedEmpty + v3.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + bin\CruiseControl\ + + + bin\CI%28NoSigning%29\ + AnyCPU + MinimumRecommendedRules.ruleset + + + bin\NoSigningForCI\ + + + + + + + \ No newline at end of file diff --git a/tSQLtCLR/XXXX.tSQLtCLR.sln.XXXX b/tSQLtCLR/XXXX.tSQLtCLR.sln.XXXX new file mode 100644 index 000000000..2b64de9fc --- /dev/null +++ b/tSQLtCLR/XXXX.tSQLtCLR.sln.XXXX @@ -0,0 +1,124 @@ +ο»Ώ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29509.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tSQLtCLR", "tSQLtCLR\tSQLtCLR.csproj", "{309A14D6-7C8A-4022-BC2C-409FD7FE48AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tSQLtTestUtilCLR", "tSQLtTestUtilCLR\tSQLtTestUtilCLR.csproj", "{87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tSQLtAssemblyKey", "tSQLtAssemblyKey\tSQLtAssemblyKey.csproj", "{09611E35-7E79-4DD3-8BCD-83F27535FB5D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnsignedEmpty", "UnsignedEmpty\UnsignedEmpty.csproj", "{14624277-A18F-4D80-BC04-FDFCD09E13D8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CruiseControl|Any CPU = CruiseControl|Any CPU + CruiseControl|Mixed Platforms = CruiseControl|Mixed Platforms + CruiseControl|x86 = CruiseControl|x86 + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + NoSigningForCI|Any CPU = NoSigningForCI|Any CPU + NoSigningForCI|Mixed Platforms = NoSigningForCI|Mixed Platforms + NoSigningForCI|x86 = NoSigningForCI|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|Any CPU.ActiveCfg = CruiseControl|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|Any CPU.Build.0 = CruiseControl|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|Mixed Platforms.ActiveCfg = CruiseControl|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|Mixed Platforms.Build.0 = CruiseControl|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|x86.ActiveCfg = CruiseControl|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|Any CPU.ActiveCfg = CruiseControl|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|Any CPU.Build.0 = CruiseControl|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|x86.ActiveCfg = Debug|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.NoSigningForCI|Any CPU.ActiveCfg = NoSigningForCI|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.NoSigningForCI|Any CPU.Build.0 = NoSigningForCI|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.NoSigningForCI|Mixed Platforms.ActiveCfg = NoSigningForCI|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.NoSigningForCI|Mixed Platforms.Build.0 = NoSigningForCI|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.NoSigningForCI|x86.ActiveCfg = NoSigningForCI|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.NoSigningForCI|x86.Build.0 = NoSigningForCI|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|Any CPU.Build.0 = Release|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|x86.ActiveCfg = Release|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|Any CPU.ActiveCfg = CruiseControl|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|Any CPU.Build.0 = CruiseControl|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|Mixed Platforms.ActiveCfg = CruiseControl|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|Mixed Platforms.Build.0 = CruiseControl|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|x86.ActiveCfg = CruiseControl|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|Any CPU.ActiveCfg = CruiseControl|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|Any CPU.Build.0 = CruiseControl|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|x86.ActiveCfg = Debug|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.NoSigningForCI|Any CPU.ActiveCfg = NoSigningForCI|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.NoSigningForCI|Any CPU.Build.0 = NoSigningForCI|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.NoSigningForCI|Mixed Platforms.ActiveCfg = NoSigningForCI|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.NoSigningForCI|Mixed Platforms.Build.0 = NoSigningForCI|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.NoSigningForCI|x86.ActiveCfg = NoSigningForCI|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.NoSigningForCI|x86.Build.0 = NoSigningForCI|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|Any CPU.Build.0 = Release|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|x86.ActiveCfg = Release|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|Any CPU.ActiveCfg = CruiseControl|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|Any CPU.Build.0 = CruiseControl|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|Mixed Platforms.ActiveCfg = CruiseControl|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|Mixed Platforms.Build.0 = CruiseControl|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|x86.ActiveCfg = CruiseControl|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|x86.Build.0 = CruiseControl|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|x86.ActiveCfg = Debug|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|x86.Build.0 = Debug|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.NoSigningForCI|Any CPU.ActiveCfg = NoSigningForCI|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.NoSigningForCI|Any CPU.Build.0 = NoSigningForCI|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.NoSigningForCI|Mixed Platforms.ActiveCfg = NoSigningForCI|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.NoSigningForCI|Mixed Platforms.Build.0 = NoSigningForCI|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.NoSigningForCI|x86.ActiveCfg = NoSigningForCI|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.NoSigningForCI|x86.Build.0 = NoSigningForCI|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|Any CPU.Build.0 = Release|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|x86.ActiveCfg = Release|Any CPU + {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|x86.Build.0 = Release|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|Any CPU.ActiveCfg = CruiseControl|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|Any CPU.Build.0 = CruiseControl|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|Mixed Platforms.ActiveCfg = Release|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|Mixed Platforms.Build.0 = Release|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|x86.ActiveCfg = Release|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|x86.ActiveCfg = Debug|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.NoSigningForCI|Any CPU.ActiveCfg = NoSigningForCI|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.NoSigningForCI|Any CPU.Build.0 = NoSigningForCI|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.NoSigningForCI|Mixed Platforms.ActiveCfg = NoSigningForCI|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.NoSigningForCI|Mixed Platforms.Build.0 = NoSigningForCI|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.NoSigningForCI|x86.ActiveCfg = NoSigningForCI|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.NoSigningForCI|x86.Build.0 = NoSigningForCI|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|Any CPU.Build.0 = Release|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {732D09E1-56A0-4FFC-A5EC-6EBCEE3F7E92} + EndGlobalSection +EndGlobal diff --git a/tSQLtCLR/tSQLtAssemblyKey/Properties/XXXX.AssemblyInfo.cs.XXXX b/tSQLtCLR/tSQLtAssemblyKey/Properties/XXXX.AssemblyInfo.cs.XXXX new file mode 100644 index 000000000..ee5616977 --- /dev/null +++ b/tSQLtCLR/tSQLtAssemblyKey/Properties/XXXX.AssemblyInfo.cs.XXXX @@ -0,0 +1,36 @@ +ο»Ώusing System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("tSQLtAssemblyKey")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("tSQLt")] +[assembly: AssemblyCopyright("Copyright Β© 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0356005b-71fd-4df3-8502-27c6a606e9e8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +// Sign assembly with key in this container: +[assembly: System.Reflection.AssemblyKeyNameAttribute("tSQLt_OfficialSigningKey")] diff --git a/tSQLtCLR/tSQLtExternalAccessKey/tSQLtExternalAccessKey.csproj b/tSQLtCLR/tSQLtAssemblyKey/XXXX.tSQLtAssemblyKey.csproj.XXXX similarity index 68% rename from tSQLtCLR/tSQLtExternalAccessKey/tSQLtExternalAccessKey.csproj rename to tSQLtCLR/tSQLtAssemblyKey/XXXX.tSQLtAssemblyKey.csproj.XXXX index de204835a..6ffe68631 100644 --- a/tSQLtCLR/tSQLtExternalAccessKey/tSQLtExternalAccessKey.csproj +++ b/tSQLtCLR/tSQLtAssemblyKey/XXXX.tSQLtAssemblyKey.csproj.XXXX @@ -2,14 +2,14 @@ Debug - x86 + AnyCPU 8.0.30703 2.0 {09611E35-7E79-4DD3-8BCD-83F27535FB5D} Library Properties - tSQLtExternalAccessKey - tSQLtExternalAccessKey + tSQLtAssemblyKey + tSQLtAssemblyKey v2.0 @@ -30,8 +30,8 @@ false true - - x86 + + AnyCPU true full false @@ -40,8 +40,8 @@ prompt 4 - - x86 + + AnyCPU pdbonly true bin\Release\ @@ -49,46 +49,23 @@ prompt 4 - - true - - - false - - - SigningKey.pfx - - - - - - bin\CruiseControl\ - - - AnyCPU - bin\Debug\ - - - AnyCPU - bin\Release\ - - AnyCPU bin\CruiseControl\ - - - bin\x86\CI%28NoSigning%29\ + TRACE + true + pdbonly AnyCPU - MinimumRecommendedRules.ruleset + prompt - - bin\CI%28NoSigning%29\ + + bin\NoSigningForCI\ + TRACE + true + pdbonly AnyCPU + prompt MinimumRecommendedRules.ruleset - - - @@ -114,6 +91,10 @@ true + + + + diff --git a/tSQLtCLR/tSQLtAssemblyKey/empty.cs b/tSQLtCLR/tSQLtAssemblyKey/empty.cs new file mode 100644 index 000000000..b92941f27 --- /dev/null +++ b/tSQLtCLR/tSQLtAssemblyKey/empty.cs @@ -0,0 +1,7 @@ +ο»Ώ +namespace tSQLtAssemblyKey +{ + class empty + { + } +} diff --git a/tSQLtCLR/tSQLtAssemblyKey/tSQLtAssemblyKey.csproj b/tSQLtCLR/tSQLtAssemblyKey/tSQLtAssemblyKey.csproj new file mode 100644 index 000000000..ea7c7ac13 --- /dev/null +++ b/tSQLtCLR/tSQLtAssemblyKey/tSQLtAssemblyKey.csproj @@ -0,0 +1,48 @@ + + + + 1 + 1 + + + + net40 + true + tSQLtAssemblyKey + The CLR for installing the SigningKey of the tSQLt unit testing framework + tSQLt Foundation + tSQLt + Copyright Β© sqlity.net 2010 - $(Year) + false + true + Release + + true + $(tSQLtOfficialSigningKey) + false + true + + + + $([System.DateTime]::Now.Year) + + + + + 2000-01-01T00:00:00Z + $([System.DateTime]::Parse($(BaseDate)).Ticks) + $([System.DateTime]::UtcNow.Ticks) + $([MSBuild]::Subtract($(NowTicks), $(BaseDateTicks))) + $([System.TimeSpan]::FromTicks($(DiffTicks)).Days) + $([System.DateTime]::Now.TimeOfDay.TotalSeconds) + $([System.Convert]::ToInt32($([MSBuild]::Divide($(CurrentTimeInSeconds), 2)))) + $(BaseVersionMajor).$(BaseVersionMinor).$(DaysSince2000).$(HalfSecondsSinceMidnight) + $(AssemblyVersion) + + + + + + + + diff --git a/tSQLtCLR/tSQLtCLR.sln b/tSQLtCLR/tSQLtCLR.sln index 144aceec0..158e0afa5 100644 --- a/tSQLtCLR/tSQLtCLR.sln +++ b/tSQLtCLR/tSQLtCLR.sln @@ -1,122 +1,40 @@ ο»Ώ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2035 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tSQLtCLR", "tSQLtCLR\tSQLtCLR.csproj", "{309A14D6-7C8A-4022-BC2C-409FD7FE48AB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tSQLtAssemblyKey", "tSQLtAssemblyKey\tSQLtAssemblyKey.csproj", "{C4788CD5-0135-4673-9DAC-A5B12A67FC2F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tSQLtTestUtilCLR", "tSQLtTestUtilCLR\tSQLtTestUtilCLR.csproj", "{87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tSQLtCLR", "tSQLtCLR\tSQLtCLR.csproj", "{14F77242-1DE1-4793-BDD6-60A745782D49}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tSQLtExternalAccessKey", "tSQLtExternalAccessKey\tSQLtExternalAccessKey.csproj", "{09611E35-7E79-4DD3-8BCD-83F27535FB5D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tSQLtTestUtilCLR", "tSQLtTestUtilCLR\tSQLtTestUtilCLR.csproj", "{293236E9-8A9F-4966-AD56-CFE395009DE7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnsignedEmpty", "UnsignedEmpty\UnsignedEmpty.csproj", "{14624277-A18F-4D80-BC04-FDFCD09E13D8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnsignedEmpty", "UnsignedEmpty\UnsignedEmpty.csproj", "{83863674-4DC7-4FB5-8E56-DDD48D350A91}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - CI(NoSigning)|Any CPU = CI(NoSigning)|Any CPU - CI(NoSigning)|Mixed Platforms = CI(NoSigning)|Mixed Platforms - CI(NoSigning)|x86 = CI(NoSigning)|x86 - CruiseControl|Any CPU = CruiseControl|Any CPU - CruiseControl|Mixed Platforms = CruiseControl|Mixed Platforms - CruiseControl|x86 = CruiseControl|x86 Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CI(NoSigning)|Any CPU.ActiveCfg = CI(NoSigning)|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CI(NoSigning)|Any CPU.Build.0 = CI(NoSigning)|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CI(NoSigning)|Mixed Platforms.ActiveCfg = CI(NoSigning)|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CI(NoSigning)|Mixed Platforms.Build.0 = CI(NoSigning)|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CI(NoSigning)|x86.ActiveCfg = CI(NoSigning)|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CI(NoSigning)|x86.Build.0 = CI(NoSigning)|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|Any CPU.ActiveCfg = CruiseControl|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|Any CPU.Build.0 = CruiseControl|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|Mixed Platforms.ActiveCfg = CruiseControl|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|Mixed Platforms.Build.0 = CruiseControl|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.CruiseControl|x86.ActiveCfg = CruiseControl|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|Any CPU.ActiveCfg = CruiseControl|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|Any CPU.Build.0 = CruiseControl|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Debug|x86.ActiveCfg = Debug|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|Any CPU.Build.0 = Release|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB}.Release|x86.ActiveCfg = Release|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CI(NoSigning)|Any CPU.ActiveCfg = CI(NoSigning)|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CI(NoSigning)|Any CPU.Build.0 = CI(NoSigning)|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CI(NoSigning)|Mixed Platforms.ActiveCfg = CI(NoSigning)|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CI(NoSigning)|Mixed Platforms.Build.0 = CI(NoSigning)|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CI(NoSigning)|x86.ActiveCfg = CI(NoSigning)|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CI(NoSigning)|x86.Build.0 = CI(NoSigning)|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|Any CPU.ActiveCfg = CruiseControl|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|Any CPU.Build.0 = CruiseControl|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|Mixed Platforms.ActiveCfg = CruiseControl|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|Mixed Platforms.Build.0 = CruiseControl|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.CruiseControl|x86.ActiveCfg = CruiseControl|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|Any CPU.ActiveCfg = CruiseControl|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|Any CPU.Build.0 = CruiseControl|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Debug|x86.ActiveCfg = Debug|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|Any CPU.Build.0 = Release|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89}.Release|x86.ActiveCfg = Release|Any CPU - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CI(NoSigning)|Any CPU.ActiveCfg = CI(NoSigning)|Any CPU - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CI(NoSigning)|Any CPU.Build.0 = CI(NoSigning)|Any CPU - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CI(NoSigning)|Mixed Platforms.ActiveCfg = CI(NoSigning)|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CI(NoSigning)|Mixed Platforms.Build.0 = CI(NoSigning)|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CI(NoSigning)|x86.ActiveCfg = CI(NoSigning)|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CI(NoSigning)|x86.Build.0 = CI(NoSigning)|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|Any CPU.ActiveCfg = CruiseControl|Any CPU - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|Any CPU.Build.0 = CruiseControl|Any CPU - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|Mixed Platforms.ActiveCfg = CruiseControl|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|Mixed Platforms.Build.0 = CruiseControl|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|x86.ActiveCfg = Release|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.CruiseControl|x86.Build.0 = Release|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|Any CPU.ActiveCfg = Debug|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|Mixed Platforms.Build.0 = Debug|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|x86.ActiveCfg = Debug|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Debug|x86.Build.0 = Debug|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|Any CPU.ActiveCfg = Release|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|Mixed Platforms.Build.0 = Release|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|x86.ActiveCfg = Release|x86 - {09611E35-7E79-4DD3-8BCD-83F27535FB5D}.Release|x86.Build.0 = Release|x86 - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CI(NoSigning)|Any CPU.ActiveCfg = CI(NoSigning)|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CI(NoSigning)|Any CPU.Build.0 = CI(NoSigning)|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CI(NoSigning)|Mixed Platforms.ActiveCfg = CI(NoSigning)|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CI(NoSigning)|Mixed Platforms.Build.0 = CI(NoSigning)|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CI(NoSigning)|x86.ActiveCfg = CI(NoSigning)|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CI(NoSigning)|x86.Build.0 = CI(NoSigning)|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|Any CPU.ActiveCfg = CruiseControl|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|Any CPU.Build.0 = CruiseControl|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|Mixed Platforms.ActiveCfg = Release|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|Mixed Platforms.Build.0 = Release|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.CruiseControl|x86.ActiveCfg = Release|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Debug|x86.ActiveCfg = Debug|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|Any CPU.Build.0 = Release|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {14624277-A18F-4D80-BC04-FDFCD09E13D8}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {732D09E1-56A0-4FFC-A5EC-6EBCEE3F7E92} + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C4788CD5-0135-4673-9DAC-A5B12A67FC2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4788CD5-0135-4673-9DAC-A5B12A67FC2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4788CD5-0135-4673-9DAC-A5B12A67FC2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4788CD5-0135-4673-9DAC-A5B12A67FC2F}.Release|Any CPU.Build.0 = Release|Any CPU + {14F77242-1DE1-4793-BDD6-60A745782D49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14F77242-1DE1-4793-BDD6-60A745782D49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14F77242-1DE1-4793-BDD6-60A745782D49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14F77242-1DE1-4793-BDD6-60A745782D49}.Release|Any CPU.Build.0 = Release|Any CPU + {293236E9-8A9F-4966-AD56-CFE395009DE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {293236E9-8A9F-4966-AD56-CFE395009DE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {293236E9-8A9F-4966-AD56-CFE395009DE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {293236E9-8A9F-4966-AD56-CFE395009DE7}.Release|Any CPU.Build.0 = Release|Any CPU + {83863674-4DC7-4FB5-8E56-DDD48D350A91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83863674-4DC7-4FB5-8E56-DDD48D350A91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83863674-4DC7-4FB5-8E56-DDD48D350A91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83863674-4DC7-4FB5-8E56-DDD48D350A91}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/tSQLtCLR/tSQLtCLR/Annotations.cs b/tSQLtCLR/tSQLtCLR/Annotations.cs new file mode 100644 index 000000000..f1248fc39 --- /dev/null +++ b/tSQLtCLR/tSQLtCLR/Annotations.cs @@ -0,0 +1,55 @@ +ο»Ώusing System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Data.SqlTypes; +using Microsoft.SqlServer.Server; +using System.Data.SqlClient; +using System.Transactions; +using System.Security; +using System.Text.RegularExpressions; + +namespace tSQLtCLR +{ + + public class Annotations + { + public Annotations() + { + } + + [SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true, IsPrecise = true, TableDefinition = "AnnotationNo INT, Annotation NVARCHAR(MAX)", FillRowMethodName = "ProcessRowForGetAnnotationList")] + public static IEnumerable GetAnnotationList([SqlFacet(MaxSize = -1)] SqlString procedureText) + { + Dictionary annotations = new Dictionary { }; + + int annotationNo = 0; + var reader = new System.IO.StringReader(GetProcedureTextAsString(procedureText)); + string line; + Regex rgx = new Regex(@"^\s*--\[@tSQLt:"); + while ((line = reader.ReadLine()) != null) + { + if (rgx.IsMatch(line)) + { + annotationNo++; + annotations.Add(annotationNo, line.Trim().Substring(2)); + } + } + + return annotations; + } + + private static string GetProcedureTextAsString(SqlString procedureText) + { + return (procedureText.IsNull) ? "" : procedureText.Value; + } + + private static void ProcessRowForGetAnnotationList(object row, out SqlInt32 AnnotationNo, out SqlString Annotation) + { + var keyValuePair = (KeyValuePair)row; + AnnotationNo = keyValuePair.Key; + Annotation = keyValuePair.Value; + } + + } +} diff --git a/tSQLtCLR/tSQLtCLR/AssemblyCopyright.cs b/tSQLtCLR/tSQLtCLR/AssemblyCopyright.cs deleted file mode 100644 index 8bb5c6259..000000000 --- a/tSQLtCLR/tSQLtCLR/AssemblyCopyright.cs +++ /dev/null @@ -1,4 +0,0 @@ -ο»Ώusing System; -using System.Reflection; - -[assembly: AssemblyCopyright("Copyright Β© sqlity.net 2010 - 2015")] \ No newline at end of file diff --git a/tSQLtCLR/tSQLtCLR/AssemblyCopyright.tt b/tSQLtCLR/tSQLtCLR/AssemblyCopyright.tt deleted file mode 100644 index 5de0ed9e8..000000000 --- a/tSQLtCLR/tSQLtCLR/AssemblyCopyright.tt +++ /dev/null @@ -1,5 +0,0 @@ -ο»Ώ<#@ template language="C#" #> -using System; -using System.Reflection; - -[assembly: AssemblyCopyright("Copyright Β© sqlity.net 2010 - <#=DateTime.Now.Year#>")] \ No newline at end of file diff --git a/tSQLtCLR/tSQLtCLR/AssemblyInfo.cs b/tSQLtCLR/tSQLtCLR/AssemblyInfo.cs deleted file mode 100644 index d61704570..000000000 --- a/tSQLtCLR/tSQLtCLR/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Data.Sql; -using System; - - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("tSQLtCLR")] -[assembly: AssemblyDescription("CLRs for the tSQLt unit testing framework")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("sqlity.net")] -[assembly: AssemblyProduct("tSQLt")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] -[assembly: CLSCompliant(true)] - -// -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.*")] - diff --git a/tSQLtCLR/tSQLtCLR/SigningKey.pfx b/tSQLtCLR/tSQLtCLR/SigningKey.pfx deleted file mode 100644 index ad8f063e8..000000000 Binary files a/tSQLtCLR/tSQLtCLR/SigningKey.pfx and /dev/null differ diff --git a/tSQLtCLR/tSQLtCLR/XXXXX.XXXX b/tSQLtCLR/tSQLtCLR/XXXXX.XXXX new file mode 100644 index 000000000..9f299389b --- /dev/null +++ b/tSQLtCLR/tSQLtCLR/XXXXX.XXXX @@ -0,0 +1,136 @@ +ο»Ώ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {309A14D6-7C8A-4022-BC2C-409FD7FE48AB} + Library + Properties + tSQLtCLR + v4.0 + tSQLtCLR + + + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + bin\CruiseControl\ + TRACE + true + pdbonly + AnyCPU + prompt + + + bin\NoSigningForCI\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + + + + + + + + + + + True + True + AssemblyCopyright.tt + + + + + + + + + + + + + + + TextTemplatingFileGenerator + AssemblyCopyright.cs + + + + false + + + false + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + + + + IF ($(ConfigurationName))==(Release) "$(SolutionDir)tSQLtCLR\PostBuild.bat" "$(SolutionDir)" "$(TargetPath)" + + \ No newline at end of file diff --git a/tSQLtCLR/tSQLtCLR/tSQLtCLR.csproj.user b/tSQLtCLR/tSQLtCLR/XXXXX.user.XXXX similarity index 100% rename from tSQLtCLR/tSQLtCLR/tSQLtCLR.csproj.user rename to tSQLtCLR/tSQLtCLR/XXXXX.user.XXXX diff --git a/tSQLtCLR/tSQLtCLR/tSQLtCLR.csproj b/tSQLtCLR/tSQLtCLR/tSQLtCLR.csproj index b2e48a705..ebce6ab0c 100644 --- a/tSQLtCLR/tSQLtCLR/tSQLtCLR.csproj +++ b/tSQLtCLR/tSQLtCLR/tSQLtCLR.csproj @@ -1,145 +1,48 @@ -ο»Ώ - + + + + 1 + 1 + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {309A14D6-7C8A-4022-BC2C-409FD7FE48AB} - Library - Properties - tSQLtCLR - v2.0 - tSQLtCLR - - - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - + net40 + true + tSQLtCLR + The CLR for the tSQLt unit testing framework + tSQLt Foundation + tSQLt + Copyright Β© sqlity.net 2010 - $(Year) + false + true + Release + true - - - false - - - SigningKey.pfx - - - bin\CruiseControl\ - TRACE - true - pdbonly - AnyCPU - prompt - - - bin\CI%28NoSigning%29\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - + $(tSQLtOfficialSigningKey) + false + true + + + + $([System.DateTime]::Now.Year) + + + + + 2000-01-01T00:00:00Z + $([System.DateTime]::Parse($(BaseDate)).Ticks) + $([System.DateTime]::UtcNow.Ticks) + $([MSBuild]::Subtract($(NowTicks), $(BaseDateTicks))) + $([System.TimeSpan]::FromTicks($(DiffTicks)).Days) + $([System.DateTime]::Now.TimeOfDay.TotalSeconds) + $([System.Convert]::ToInt32($([MSBuild]::Divide($(CurrentTimeInSeconds), 2)))) + $(BaseVersionMajor).$(BaseVersionMinor).$(DaysSince2000).$(HalfSecondsSinceMidnight) + $(AssemblyVersion) + + + + - - - - - - True - True - AssemblyCopyright.tt - - - - - - - - - - - - - - - TextTemplatingFileGenerator - AssemblyCopyright.cs - - - - - false - - - false - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - - - - IF ($(ConfigurationName))==(Release) "$(SolutionDir)tSQLtCLR\PostBuild.bat" "$(SolutionDir)" "$(TargetPath)" - - \ No newline at end of file + diff --git a/tSQLtCLR/tSQLtExternalAccessKey/SigningKey.pfx b/tSQLtCLR/tSQLtExternalAccessKey/SigningKey.pfx deleted file mode 100644 index ad8f063e8..000000000 Binary files a/tSQLtCLR/tSQLtExternalAccessKey/SigningKey.pfx and /dev/null differ diff --git a/tSQLtCLR/tSQLtTestUtilCLR/ClrStoredProcedures.cs b/tSQLtCLR/tSQLtTestUtilCLR/ClrStoredProcedures.cs new file mode 100644 index 000000000..cf36112dc --- /dev/null +++ b/tSQLtCLR/tSQLtTestUtilCLR/ClrStoredProcedures.cs @@ -0,0 +1,15 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Text; + +namespace tSQLtTestUtilCLR +{ + public class ClrStoredProcedures + { + public static void AClrSsp() + { + return; + } + + } +} diff --git a/tSQLtCLR/tSQLtTestUtilCLR/DataTypeByteOrdered.cs b/tSQLtCLR/tSQLtTestUtilCLR/DataTypeByteOrdered.cs index 0f6a1a458..6e06ebf03 100644 --- a/tSQLtCLR/tSQLtTestUtilCLR/DataTypeByteOrdered.cs +++ b/tSQLtCLR/tSQLtTestUtilCLR/DataTypeByteOrdered.cs @@ -14,7 +14,7 @@ public class DataTypeByteOrdered : INullable, IBinarySerialize, IComparable public override string ToString() { - return "<>"; + return $"<>"; } public int CompareTo(object obj) diff --git a/tSQLtCLR/tSQLtTestUtilCLR/Properties/Settings.Designer.cs b/tSQLtCLR/tSQLtTestUtilCLR/Properties/Settings.Designer.cs deleted file mode 100644 index 51ce7315d..000000000 --- a/tSQLtCLR/tSQLtTestUtilCLR/Properties/Settings.Designer.cs +++ /dev/null @@ -1,26 +0,0 @@ -ο»Ώ//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace tSQLtTestUtilCLR.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - } -} diff --git a/tSQLtCLR/tSQLtTestUtilCLR/Properties/Settings.settings b/tSQLtCLR/tSQLtTestUtilCLR/Properties/Settings.settings deleted file mode 100644 index 049245f40..000000000 --- a/tSQLtCLR/tSQLtTestUtilCLR/Properties/Settings.settings +++ /dev/null @@ -1,6 +0,0 @@ -ο»Ώ - - - - - diff --git a/tSQLtCLR/tSQLtTestUtilCLR/Properties/AssemblyInfo.cs b/tSQLtCLR/tSQLtTestUtilCLR/Properties/XXX.AssemblyInfo.cs.XXXX similarity index 86% rename from tSQLtCLR/tSQLtTestUtilCLR/Properties/AssemblyInfo.cs rename to tSQLtCLR/tSQLtTestUtilCLR/Properties/XXX.AssemblyInfo.cs.XXXX index 494439a2b..df16e6cfe 100644 --- a/tSQLtCLR/tSQLtTestUtilCLR/Properties/AssemblyInfo.cs +++ b/tSQLtCLR/tSQLtTestUtilCLR/Properties/XXX.AssemblyInfo.cs.XXXX @@ -35,4 +35,7 @@ [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: System.Security.AllowPartiallyTrustedCallers] \ No newline at end of file +[assembly: System.Security.AllowPartiallyTrustedCallers] + +// Sign assembly with key in this container: +[assembly: System.Reflection.AssemblyKeyNameAttribute("tSQLt_OfficialSigningKey")] diff --git a/tSQLtCLR/tSQLtTestUtilCLR/SigningKey.pfx b/tSQLtCLR/tSQLtTestUtilCLR/SigningKey.pfx deleted file mode 100644 index ad8f063e8..000000000 Binary files a/tSQLtCLR/tSQLtTestUtilCLR/SigningKey.pfx and /dev/null differ diff --git a/tSQLtCLR/tSQLtTestUtilCLR/app.config b/tSQLtCLR/tSQLtTestUtilCLR/XXXX.app.config.XXXX similarity index 100% rename from tSQLtCLR/tSQLtTestUtilCLR/app.config rename to tSQLtCLR/tSQLtTestUtilCLR/XXXX.app.config.XXXX diff --git a/tSQLtCLR/tSQLtTestUtilCLR/XXXX.tSQLtTestUtilCLR.csproj.XXXX b/tSQLtCLR/tSQLtTestUtilCLR/XXXX.tSQLtTestUtilCLR.csproj.XXXX new file mode 100644 index 000000000..977061726 --- /dev/null +++ b/tSQLtCLR/tSQLtTestUtilCLR/XXXX.tSQLtTestUtilCLR.csproj.XXXX @@ -0,0 +1,85 @@ +ο»Ώ + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89} + Library + Properties + tSQLtTestUtilCLR + tSQLtTestUtilCLR + v2.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + bin\CruiseControl\ + TRACE + true + pdbonly + AnyCPU + prompt + + + bin\NoSigningForCI\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + \ No newline at end of file diff --git a/tSQLtCLR/tSQLtTestUtilCLR/tSQLtTestUtilCLR.csproj b/tSQLtCLR/tSQLtTestUtilCLR/tSQLtTestUtilCLR.csproj index 12089adeb..4adcf45bb 100644 --- a/tSQLtCLR/tSQLtTestUtilCLR/tSQLtTestUtilCLR.csproj +++ b/tSQLtCLR/tSQLtTestUtilCLR/tSQLtTestUtilCLR.csproj @@ -1,94 +1,46 @@ -ο»Ώ - + + + + 1 + 1 + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {87E64BC4-089C-4E9F-BCB2-DB12BDB5FB89} - Library - Properties - tSQLtTestUtilCLR - tSQLtTestUtilCLR - v2.0 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - bin\CruiseControl\ - TRACE - true - pdbonly - AnyCPU - prompt - - + net40 + true + tSQLtTestUtilCLR + The CLR used for testing the tSQLt unit testing framework + tSQLt Foundation + tSQLt + Copyright Β© sqlity.net 2010 - $(Year) + false + true + Release + true - - - false - - - SigningKey.pfx - - - bin\CI%28NoSigning%29\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - - - - - - - - - - - True - True - Settings.settings - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - \ No newline at end of file + + + +