88 type : string
99 required : true
1010 solution_name :
11- description : " Solution file to build and test"
11+ description : " Solution file to build and test (.slnx) "
1212 required : true
1313 type : string
1414
1515jobs :
1616 test :
1717 runs-on : ubuntu-latest
1818
19+ strategy :
20+ fail-fast : false
21+ matrix :
22+ include :
23+ - tfm : net8.0
24+ sdk : 8.0.x
25+ - tfm : net9.0
26+ sdk : 9.0.x
27+ - tfm : net10.0
28+ sdk : 10.0.x
29+
1930 env :
2031 SOLUTION_NAME : ${{ inputs.solution_name }}
21- BRANCH_NAME : ${{ github.head_ref || github.ref_name }}
32+ BRANCH_NAME : ${{ inputs.branch }}
2233
2334 steps :
2435 - name : Set Build Configuration (Release if main)
36+ shell : bash
2537 run : |
26- if [ "${{ env.BRANCH_NAME }}" = "main" ]; then
38+ set -euo pipefail
39+ b="${{ env.BRANCH_NAME }}"
40+ if [[ "$b" == "main" || "$b" == "refs/heads/main" || "$b" == */main ]]; then
2741 echo "BUILD_CONFIGURATION=Release" >> $GITHUB_ENV
2842 else
2943 echo "BUILD_CONFIGURATION=Debug" >> $GITHUB_ENV
@@ -35,97 +49,81 @@ jobs:
3549 ref : ${{ inputs.branch }}
3650 fetch-depth : 0
3751
38- - name : Setup .NET SDKs
52+ - name : Setup .NET SDKs (10 + matrix)
3953 uses : actions/setup-dotnet@v4
4054 with :
4155 dotnet-version : |
4256 10.0.x
43- 9.0.x
44- 8.0.x
57+ ${{ matrix.sdk }}
4558
46- - name : Restore Dependencies
47- run : dotnet restore ${{ env.SOLUTION_NAME }}
59+ - name : Validate solution exists
60+ shell : bash
61+ run : |
62+ set -euo pipefail
63+ if [[ ! -f "${{ env.SOLUTION_NAME }}" ]]; then
64+ echo "❌ Solution not found: ${{ env.SOLUTION_NAME }}"
65+ echo "Repo root contents:"; ls -la
66+ exit 1
67+ fi
4868
49- - name : Build
50- run : dotnet build --no- restore --configuration ${{ env.BUILD_CONFIGURATION }} ${{ env. SOLUTION_NAME }}
69+ - name : Restore (once)
70+ run : dotnet restore " ${{ env.SOLUTION_NAME }}"
5171
52- - name : Run Tests
72+ - name : Run solution test projects for ${{ matrix.tfm }}
73+ shell : bash
5374 run : |
54- mkdir -p TestResults
55- found=false
75+ set -euo pipefail
5676
57- tfm_file="./TestResults/_tfms.txt"
58- : > "$tfm_file"
77+ sln="${{ env.SOLUTION_NAME }}"
78+ tfm="${{ matrix.tfm }}"
79+ mkdir -p "TestResults/$tfm"
5980
60- for proj in $(find . -name "*.Tests.csproj"); do
61- found=true
62- frameworks=$(grep -oPm1 "(?<=<TargetFrameworks>)[^<]+" "$proj" || true)
63- if [ -z "$frameworks" ]; then
64- frameworks=$(grep -oPm1 "(?<=<TargetFramework>)[^<]+" "$proj" || true)
65- fi
81+ # Get only test projects from the solution (fast)
82+ mapfile -t test_projects < <(
83+ dotnet slnx "$slnx" list | sed '1,2d' | sed '/^\s*$/d' | grep -E '\.Tests\.csproj$' || true
84+ )
6685
67- for fw in $(echo $frameworks | tr ';' ' '); do
68- echo "$fw" >> "$tfm_file"
69-
70- results_dir="./TestResults/$fw"
71- mkdir -p "$results_dir"
72- dotnet test "$proj" \
73- --no-build \
74- --configuration ${{ env.BUILD_CONFIGURATION }} \
75- --framework $fw \
76- --logger "trx;LogFileName=$(basename $proj)-$fw.trx" \
77- --results-directory "$results_dir"
78- done
79- done
80-
81- if [ "$found" = false ]; then
82- echo "⚠️ No test projects (*.Tests.csproj) were found."
86+ if [[ "${#test_projects[@]}" -eq 0 ]]; then
87+ echo "❌ No test projects (*.Tests.csproj) found in solution."
8388 exit 1
8489 fi
8590
86- sort -u "$tfm_file" -o "$tfm_file"
87- echo "Detected TFMs:"
88- cat "$tfm_file"
91+ ran_any=false
8992
90- if grep -qx "net10.0" "$tfm_file"; then
91- echo "PRIMARY_TFM=net10.0" >> $GITHUB_ENV
92- elif grep -qx "net9.0" "$tfm_file"; then
93- echo "PRIMARY_TFM=net9.0" >> $GITHUB_ENV
94- else
95- echo "PRIMARY_TFM=" >> $GITHUB_ENV
96- fi
93+ for proj in "${test_projects[@]}"; do
94+ # Determine TargetFrameworks
95+ frameworks=$(grep -oPm1 "(?<=<TargetFrameworks>)[^<]+" "$proj" || true)
96+ if [[ -z "$frameworks" ]]; then
97+ frameworks=$(grep -oPm1 "(?<=<TargetFramework>)[^<]+" "$proj" || true)
98+ fi
9799
98- - name : Ensure PRIMARY Results Exist
99- if : ${{ env.PRIMARY_TFM != '' }}
100- run : |
101- mkdir -p "./TestResults/${{ env.PRIMARY_TFM }}"
102- if ! compgen -G "./TestResults/${{ env.PRIMARY_TFM }}/*.trx" > /dev/null; then
103- echo "⚠️ No ${{ env.PRIMARY_TFM }} test results" > "./TestResults/${{ env.PRIMARY_TFM }}/placeholder.txt"
104- fi
100+ if ! echo "$frameworks" | tr ';' '\n' | grep -qx "$tfm"; then
101+ continue
102+ fi
105103
106- - name : Upload PRIMARY Test Results
107- if : ${{ env.PRIMARY_TFM != '' }}
108- uses : actions/upload-artifact@v4
109- with :
110- name : test-results-${{ env.PRIMARY_TFM }}
111- # only upload trx + placeholder, ignore everything else
112- path : |
113- ./TestResults/${{ env.PRIMARY_TFM }}/*.trx
114- ./TestResults/${{ env.PRIMARY_TFM }}/placeholder.txt
115- overwrite : true
104+ ran_any=true
105+ name="$(basename "$proj" .csproj)"
116106
117- - name : Ensure .NET 8.0 Results Exist
118- run : |
119- mkdir -p ./TestResults/net8.0
120- if ! compgen -G "./TestResults/net8.0/*.trx" > /dev/null; then
121- echo "⚠️ No .NET 8.0 test results" > ./TestResults/net8.0/placeholder.txt
107+ dotnet test "$proj" \
108+ --no-restore \
109+ --configuration "$BUILD_CONFIGURATION" \
110+ --framework "$tfm" \
111+ --logger "trx;LogFileName=${name}-${tfm}.trx" \
112+ --results-directory "TestResults/$tfm"
113+ done
114+
115+ if [[ "$ran_any" == "false" ]]; then
116+ echo "⚠️ No solution test projects target $tfm."
117+ echo "No $tfm test results (no solution test projects target $tfm)" > "TestResults/$tfm/placeholder.txt"
118+ exit 1
122119 fi
123120
124- - name : Upload .NET 8.0 Test Results
121+ - name : Upload ${{ matrix.tfm }} Test Results
122+ if : always()
125123 uses : actions/upload-artifact@v4
126124 with :
127- name : test-results-net8.0
125+ name : test-results-${{ matrix.tfm }}
128126 path : |
129- ./ TestResults/net8.0 /*.trx
130- ./ TestResults/net8.0 /placeholder.txt
127+ TestResults/${{ matrix.tfm }} /*.trx
128+ TestResults/${{ matrix.tfm }} /placeholder.txt
131129 overwrite : true
0 commit comments