#!/usr/bin/env bash

# this file tests templates created by
# dotnet new <template>

set -euo pipefail
IFS=$'\n\t'
set -x

IFS='.-' read -ra VERSION_SPLIT <<< "$1"
declare -a allTemplates
major_version="${VERSION_SPLIT[0]}"

# The list of templates in each version of .NET that we want to test.
# If additional templates are found via `dotnet new --list`, this test
# will fail unless they are added here.

dotnet10Templates=(
    apicontroller
    blazor
    blazorwasm
    buildprops
    buildtargets
    classlib
    console
    editorconfig
    .editorconfig
    gitattributes
    .gitattributes
    gitignore
    .gitignore
    global.json
    globaljson
    grpc
    mstest-class
    mstest
    mvc
    mvccontroller
    nuget.config
    nugetconfig
    nunit
    nunit-test
    packagesprops
    page
    proto
    razor
    razorclasslib
    razorcomponent
    sln
    solution
    tool-manifest
    view
    viewimports
    viewstart
    web
    webapi
    webapiaot
    webapp
    webconfig
    worker
    xunit
)

dotnet9Templates=(
    apicontroller
    blazor
    blazorwasm
    buildprops
    buildtargets
    classlib
    console
    editorconfig
    .editorconfig
    gitignore
    .gitignore
    global.json
    globaljson
    grpc
    mstest-class
    mstest
    mvc
    mvccontroller
    nuget.config
    nugetconfig
    nunit
    nunit-test
    packagesprops
    page
    proto
    razor
    razorclasslib
    razorcomponent
    sln
    solution
    tool-manifest
    view
    viewimports
    viewstart
    web
    webapi
    webapiaot
    webapp
    webconfig
    worker
    xunit
)

dotnet8Templates=(
    apicontroller
    blazor
    blazorwasm
    buildprops
    buildtargets
    classlib
    console
    editorconfig
    .editorconfig
    gitignore
    .gitignore
    globaljson
    global.json
    grpc
    mstest
    mvc
    mvccontroller
    nugetconfig
    nuget.config
    nunit
    nunit-test
    page
    proto
    razor
    razorclasslib
    razorcomponent
    sln
    solution
    tool-manifest
    view
    viewimports
    viewstart
    web
    webapi
    webapiaot
    webapp
    webconfig
    worker
    xunit
)

dotnet7Templates=(
    angular
    blazorserver
    blazorserver-empty
    blazorwasm
    blazorwasm-empty
    classlib
    console
    editorconfig
    gitignore
    globaljson
    grpc
    mstest
    mvc
    nugetconfig
    nunit
    nunit-test
    page
    proto
    razor
    razorclasslib
    razorcomponent
    react
    sln
    solution
    tool-manifest
    viewimports
    viewstart
    web
    webapi
    webapp
    webconfig
    worker
    xunit
)

dotnet6Templates=(
    angular
    blazorserver
    blazorwasm
    classlib
    console
    editorconfig
    gitignore
    globaljson
    grpc
    mstest
    mvc
    nugetconfig
    nunit
    nunit-test
    page
    proto
    razor
    razorclasslib
    razorcomponent
    react
    sln
    solution
    tool-manifest
    viewimports
    viewstart
    web
    webapi
    webapp
    webconfig
    worker
    xunit
)

dotnet3Templates=(
    angular
    blazorserver
    classlib
    console
    gitignore
    globaljson
    grpc
    mstest
    mvc
    nugetconfig
    nunit
    nunit-test
    page
    proto
    razorclasslib
    razorcomponent
    react
    reactredux
    sln
    tool-manifest
    viewimports
    viewstart
    web
    webapi
    webapp
    webconfig
    worker
    xunit
)


# All templates are tested with "dotnet template new", but this list
# adds further actions (eg, build, run) for some templates. These
# templates are only tested if `dotnet new --list` shows the template
# for this .NET version.
#
# format: <template> <action>
# actions:
#    new - just create template ( using dotnet new <template> )
#    project,new - create a project, and then create this template ( using dotnet new <template> )
#    build - create template and run build on it ( dotnet build )
#    run - create template and run it ( dotnet run )
#    test - create template and run its tests ( dotnet test )

templateActions=\
"api build
apicontroller new
angular build
blazor build
blazorserver build
blazorserver-empty build
blazorwasm build
blazorwasm-empty build
buildprops new
buildtargets new
classlib build
console run
editorconfig new
.editorconfig new
gitignore new
.gitignore new
globaljson new
global.json new
mstest test
mstest-class project,new
mvc build
nunit test
nunit-test new
page new
razor build
razorclasslib build
react build
reactredux build
sln new
view new
web build
webapi build
webapp build
worker build
xunit test"

template9Actions=\
"nunit-test project,new"
template10Actions=\
"nunit-test project,new"

# Templates that can be ignored. They may be present in the dotnet new
# --list output but are safe to ignore. We we don't want to test these
# because they are known to not work on the platforms we care about.
templateIgnoreList=(
    # mcpserver fails on s390x and ppc64le due to missing dependencies in the template
    mcpserver
    # playwright needs powershell and needs to be interactively used to install browser integration pieces
    mstest-playwright
    nunit-playwright
    winforms
    winformscontrollib
    winformslib
    wpf
    wpfcustomcontrollib
    wpflib
    wpfusercontrollib
)


declare -A knownTemplateActions
readarray -t templateLines <<< "$templateActions"
for line in "${templateLines[@]}"; do
    templateName="${line%% *}"
    action="${line##* }"
    knownTemplateActions["$templateName"]="$action"
done

templateActions="template${major_version}Actions"
templateActions="${!templateActions:-}"
if [[ $templateActions != "" ]]; then
    readarray -t templateLines <<< "${templateActions}"
    for line in "${templateLines[@]}"; do
        templateName="${line%% *}"
        action="${line##* }"
        knownTemplateActions["$templateName"]="$action"
    done
fi

dotnet help > /dev/null 2>/dev/null

dotnet new --list
readarray -t allAutoTemplates < <(
    dotnet new --list |
        sed '0,/^--*/d' |
        awk -F '  +' ' { print $2 }' |
        sed 's/,/\n/' |
        sed -e '/^ *$/d' |
        sort -u)

if [[ "${#allAutoTemplates[@]}" -lt 10 ]]; then
    echo "unable to parse 'dotnet new --list' correctly"
    exit 1
fi

set +x
filteredAutoTemplates=()
for template in "${allAutoTemplates[@]}"; do
    ignore=0
    for ignoreTemplate in "${templateIgnoreList[@]}"; do
        if [[ $ignoreTemplate == "$template" ]]; then
            ignore=1
        fi
    done
    if [[ $ignore == 0 ]]; then
        filteredAutoTemplates+=("$template")
    fi
done
set -x

template_names="dotnet${major_version}Templates[@]"
allTemplates=( "${!template_names}" )
if [[ -z "$allTemplates" ]] ; then
    echo "error: unknown dotnet version " "${VERSION_SPLIT[@]}"
    echo "Need a new template list for this. Here's a starting point:"
    echo "${filteredAutoTemplates[@]}"
    exit 1
fi

set +x
for autoTemplate in "${filteredAutoTemplates[@]}"; do
    found=0
    for explicitTemplate in "${allTemplates[@]}"; do
        if [[ $explicitTemplate == "$autoTemplate" ]]; then
            found=1
        fi
    done
    if [[ $found == 0 ]]; then
        echo "error: auto-detected template '${autoTemplate}' which is not in" \
             "the explicit list of templates to test. If this is intentional," \
             "add it to the templateIgnoreList."
        echo "detected templates:" "${filteredAutoTemplates[@]}"
        exit 1

    fi
done
set -x

echo "Auto-detected templates:" "${allAutoTemplates[@]}"
echo "Ignoring:" "${templateIgnoreList[@]}"
echo "Testing:" "${allTemplates[@]}"
echo

tmpDir=""
templateName=""

failedTests=0

function cleanupFunc {
    if [ -n "${tmpDir:-}" ] ; then
        rm -rf "${tmpDir}"
    fi
}

function testTemplate {
    local templateName="${1}"
    local action="${2}"

    echo "### Testing: ${templateName}"

    if [[ ( $(uname -m) == "s390x" ||  $(uname -m) == "ppc64le" || $(uname -m) == "armv7" || $(uname -m) == "armv8l" ) && ( "${templateName}" == "webapiaot" ) ]]; then
        # webapiaot implies AOT which will not even restore on mono (ppc64le/s390x) and arm, skip it
        echo "SKIP skipping webapiaot on ppc64/s390x/armv7"
        return
    fi

    if [[ ${action} = project,* ]] ; then
        dotnet new console 2>&1 | tee "${templateName}.log"
        action=$(echo "${action}" | sed -E "s|project,||")
    fi

    dotnet new "${templateName}" 2>&1 | tee "${templateName}.log"
    if grep -i failed "${templateName}.log"; then
        echo "error: ${templateName} failed."
        failedTests=$((failedTests+1))
        return
    fi

    allCsprojs=(./*.csproj)
    firstCsproj=${allCsprojs[0]}
    if [ -f "${firstCsproj}" ]; then
        # Treat warnings as errors to catch template quality regressions, but
        # exclude warnings that reflect the environment rather than the template:
        # NU1900-NU1905 (NuGet audit/vulnerability data), NU1803 (non-HTTPS source),
        # NETSDK1057 (preview SDK).
        sed -i '0,/<PropertyGroup>/s/<PropertyGroup>/<PropertyGroup><TreatWarningsAsErrors>true<\/TreatWarningsAsErrors><WarningsNotAsErrors>NETSDK1057;NU1803;NU1900;NU1901;NU1902;NU1903;NU1904;NU1905<\/WarningsNotAsErrors>/' "${firstCsproj}"
    fi

    if [ "${action}" = "new" ] ; then
        true # no additional action
    elif [[ ( $(uname -m) == "s390x" ||  $(uname -m) == "ppc64le" ) && ( "${templateName}" == "angular" ) ]]; then
        # angular needs the node module fsevents, which is not supported on s390x
        true
    elif [ "${action}" = "build" ] || [ "${action}" = "run" ] || [ "${action}" = "test" ] ; then
        if [[ ( $(uname -m) == "s390x" ) && ( "${templateName}" == "xunit" || "${templateName}" == "nunit" || "${templateName}" == "mstest" ) ]]; then
            # xunit/nunit/mstest need a package version fix for s390x;
            # the default templates are known to be broken out of the
            # box
            sed -i -E 's|(PackageReference Include="Microsoft.NET.Test.Sdk" Version=")16.11.0"|\117.0.0"|' ./*.csproj
        elif [[ ( $(uname -m) == "ppc64le" ) && ( "${templateName}" == "xunit" || "${templateName}" == "nunit" || "${templateName}" == "mstest" ) ]]; then
            # xunit/nunit/mstest need a package version fix for a version with enablement for ppc64le;
            sed -i -E 's|(PackageReference Include="Microsoft.NET.Test.Sdk" Version=")17.3.2"|\117.5.0"|' ./*.csproj
        fi
        if ! dotnet "${action}"; then
            failedTests=$((failedTests+1))
            echo "FAILED: ${templateName} failed"
        fi
    else
        echo "error: unknown action ${action}"
        exit 1
    fi
    return
}

function testTemplates {
    local templateName
    local action

    for templateName in "${allTemplates[@]}"; do
        action="${knownTemplateActions[$templateName]:-new}"

        mkdir -p "${tmpDir}/${templateName}_template"
        pushd "${tmpDir}/${templateName}_template" > /dev/null

        testTemplate "${templateName}" "${action}"

        popd > /dev/null
    done

    if [[ $failedTests == 0 ]]; then
        echo "OK: all template tests passed."
        exit 0
    else
        echo "error: $failedTests tests failed"
        exit 1
    fi
}

trap cleanupFunc EXIT
# This test creates multiple files that can take more than 1 GiB of disk space.
# That's not suitable for use with /tmp/ which is often small and
# memory-backed:
# https://fedoraproject.org/wiki/Features/tmp-on-tmpfs#Comments_and_Discussion
tmpDir="$( mktemp -d --tmpdir=/var/tmp )"

scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
if [[ -f "${scriptDir}/nuget.config" ]]; then
    cp "${scriptDir}/nuget.config" "${tmpDir}/"
fi

testTemplates
