1717 DOTNET_CLI_TELEMETRY_OPTOUT : true
1818 DOTNET_NOLOGO : true
1919 CI_TEST_FILTER : ' Category!=Debug&Category!=Manual&Category!=Performance'
20- MIN_LINE_COVERAGE_PERCENT : ' 18'
2120
2221jobs :
2322 build :
24- name : Build & Test
23+ name : Build and Test
2524 runs-on : ${{ matrix.os }}
2625 timeout-minutes : 45
2726 strategy :
2827 fail-fast : false
2928 matrix :
3029 os : [ ubuntu-latest, windows-latest, macos-latest ]
31-
30+
3231 steps :
3332 - name : Checkout
3433 uses : actions/checkout@v7
3534 with :
3635 fetch-depth : 0
37-
36+
3837 - name : Setup .NET 10
3938 uses : actions/setup-dotnet@v6
4039 with :
4140 dotnet-version : ' 10.0.x'
42-
41+
4342 - name : Display .NET info
4443 run : dotnet --info
45-
44+
4645 - name : Restore dependencies
4746 run : dotnet restore SharpCoreDB.CI.slnf --configfile NuGet.Config /p:UseLocalProjectReferences=true
48-
47+
4948 - name : Fail on deprecated NuGet packages
5049 shell : pwsh
5150 run : |
6463 }
6564
6665 Write-Host "No deprecated NuGet packages detected."
67-
66+
6867 - name : Fail on vulnerable NuGet packages
6968 shell : pwsh
7069 run : |
@@ -83,134 +82,85 @@ jobs:
8382 }
8483
8584 Write-Host "No vulnerable NuGet packages detected."
86-
85+
8786 - name : Build
8887 run : dotnet build SharpCoreDB.CI.slnf --configuration Release --no-restore /p:ContinuousIntegrationBuild=true /p:UseLocalProjectReferences=true
8988
89+ # MTP (xunit.v3) test hosts on .NET 10 no longer support `dotnet test`'s VSTest flags
90+ # (e.g. --collect "XPlat Code Coverage", --logger trx, --blame-hang). CI runs the MTP
91+ # executable directly, which understands the same VSTest filter expression and writes TRX.
9092 - name : Test SharpCoreDB.Tests
91- run : dotnet test tests/SharpCoreDB.Tests/SharpCoreDB.Tests.csproj --configuration Release --no-build --verbosity minimal --logger trx --results-directory ./TestResults/SharpCoreDB.Tests --collect:"XPlat Code Coverage" --filter "${{ env.CI_TEST_FILTER }}" --blame-hang --blame-hang-timeout 10m
93+ shell : bash
94+ run : |
95+ set -euo pipefail
96+ mkdir -p ./TestResults/SharpCoreDB.Tests
97+ tests/SharpCoreDB.Tests/bin/Release/net10.0/SharpCoreDB.Tests \
98+ -filterVSTest "${{ env.CI_TEST_FILTER }}" \
99+ -result-trx "./TestResults/SharpCoreDB.Tests/SharpCoreDB.Tests.trx"
92100 timeout-minutes : 30
93101 env :
94102 CI : " true"
95103 GITHUB_ACTIONS : " true"
96104
97105 - name : Test SharpCoreDB.VectorSearch.Tests
98- run : dotnet test tests/SharpCoreDB.VectorSearch.Tests/SharpCoreDB.VectorSearch.Tests.csproj --configuration Release --no-build --verbosity minimal --logger trx --results-directory ./TestResults/SharpCoreDB.VectorSearch.Tests --collect:"XPlat Code Coverage" --filter "${{ env.CI_TEST_FILTER }}" --blame-hang --blame-hang-timeout 10m
106+ shell : bash
107+ run : |
108+ set -euo pipefail
109+ mkdir -p ./TestResults/SharpCoreDB.VectorSearch.Tests
110+ tests/SharpCoreDB.VectorSearch.Tests/bin/Release/net10.0/SharpCoreDB.VectorSearch.Tests \
111+ -filterVSTest "${{ env.CI_TEST_FILTER }}" \
112+ -result-trx "./TestResults/SharpCoreDB.VectorSearch.Tests/SharpCoreDB.VectorSearch.Tests.trx"
99113 timeout-minutes : 15
100114 env :
101115 CI : " true"
102116 GITHUB_ACTIONS : " true"
103117
104118 - name : Test SharpCoreDB.EntityFrameworkCore.Tests
105- run : dotnet test tests/SharpCoreDB.EntityFrameworkCore.Tests/SharpCoreDB.EntityFrameworkCore.Tests.csproj --configuration Release --no-build --verbosity minimal --logger trx --results-directory ./TestResults/SharpCoreDB.EntityFrameworkCore.Tests --collect:"XPlat Code Coverage" --filter "${{ env.CI_TEST_FILTER }}" --blame-hang --blame-hang-timeout 10m
119+ shell : bash
120+ run : |
121+ set -euo pipefail
122+ mkdir -p ./TestResults/SharpCoreDB.EntityFrameworkCore.Tests
123+ tests/SharpCoreDB.EntityFrameworkCore.Tests/bin/Release/net10.0/SharpCoreDB.EntityFrameworkCore.Tests \
124+ -filterVSTest "${{ env.CI_TEST_FILTER }}" \
125+ -result-trx "./TestResults/SharpCoreDB.EntityFrameworkCore.Tests/SharpCoreDB.EntityFrameworkCore.Tests.trx"
106126 timeout-minutes : 15
107127 env :
108128 CI : " true"
109129 GITHUB_ACTIONS : " true"
110130
111131 - name : Test SharpCoreDB.Functional.Linq2DB.Tests
112- run : dotnet test tests/SharpCoreDB.Functional.Linq2DB.Tests/SharpCoreDB.Functional.Linq2DB.Tests.csproj --configuration Release --no-build --verbosity minimal --logger trx --results-directory ./TestResults/SharpCoreDB.Functional.Linq2DB.Tests --collect:"XPlat Code Coverage" --filter "${{ env.CI_TEST_FILTER }}" --blame-hang --blame-hang-timeout 10m
132+ shell : bash
133+ run : |
134+ set -euo pipefail
135+ mkdir -p ./TestResults/SharpCoreDB.Functional.Linq2DB.Tests
136+ tests/SharpCoreDB.Functional.Linq2DB.Tests/bin/Release/net10.0/SharpCoreDB.Functional.Linq2DB.Tests \
137+ -filterVSTest "${{ env.CI_TEST_FILTER }}" \
138+ -result-trx "./TestResults/SharpCoreDB.Functional.Linq2DB.Tests/SharpCoreDB.Functional.Linq2DB.Tests.trx"
113139 timeout-minutes : 10
114140 env :
115141 CI : " true"
116142 GITHUB_ACTIONS : " true"
117143
118- - name : Validate coverage threshold
119- if : matrix.os == 'ubuntu-latest'
120- shell : python3 {0}
121- run : |
122- import glob
123- import os
124- import sys
125- import xml.etree.ElementTree as ET
126-
127- threshold = float(os.environ.get("MIN_LINE_COVERAGE_PERCENT", "60"))
128- files = glob.glob("**/TestResults/**/coverage.cobertura.xml", recursive=True)
129-
130- if not files:
131- print("No coverage.cobertura.xml files found.", file=sys.stderr)
132- sys.exit(1)
133-
134- line_hits_by_file = {}
135- for file in files:
136- try:
137- root = ET.parse(file).getroot()
138- lines_valid = int(root.attrib.get("lines-valid", "0"))
139- lines_covered = int(root.attrib.get("lines-covered", "0"))
140- print(f"Coverage input: {file} -> {lines_covered}/{lines_valid}")
141-
142- for class_element in root.findall(".//class"):
143- filename = class_element.attrib.get("filename")
144- if not filename:
145- continue
146-
147- file_hits = line_hits_by_file.setdefault(filename, {})
148- for line_element in class_element.findall("./lines/line"):
149- number_text = line_element.attrib.get("number")
150- if number_text is None:
151- continue
152-
153- try:
154- line_number = int(number_text)
155- except ValueError:
156- continue
157-
158- hits = int(line_element.attrib.get("hits", "0"))
159- file_hits[line_number] = max(file_hits.get(line_number, 0), hits)
160- except Exception as ex:
161- print(f"Skipping unreadable coverage file {file}: {ex}")
162-
163- valid = sum(len(file_hits) for file_hits in line_hits_by_file.values())
164- covered = sum(
165- 1
166- for file_hits in line_hits_by_file.values()
167- for hits in file_hits.values()
168- if hits > 0
169- )
170-
171- if valid == 0:
172- print("Coverage reports found but no line entries could be merged.", file=sys.stderr)
173- sys.exit(1)
174-
175- percent = (covered / valid) * 100.0
176- print(f"Merged unique line coverage: {percent:.2f}% (threshold: {threshold:.2f}%)")
177-
178- if percent < threshold:
179- print("Coverage threshold not met.", file=sys.stderr)
180- sys.exit(1)
181-
182144 - name : Upload test results
183145 if : always()
184146 uses : actions/upload-artifact@v4
185147 with :
186148 name : test-results-${{ matrix.os }}
187149 path : ' **/TestResults/**/*.trx'
188150 if-no-files-found : ignore
189-
190- - name : Upload coverage
191- if : matrix.os == 'ubuntu-latest'
192- uses : codecov/codecov-action@v6
193- with :
194- files : ' **/TestResults/**/coverage.cobertura.xml'
195- flags : unittests
196- name : codecov-umbrella
197- fail_ci_if_error : false
198- token : ${{ secrets.CODECOV_TOKEN }}
199- disable_search : false
200- verbose : true
201151
202152 pack :
203153 name : Pack NuGet Packages
204154 runs-on : ubuntu-latest
205155 needs : build
206156 if : github.event_name == 'push' && github.ref == 'refs/heads/master'
207-
157+
208158 steps :
209159 - name : Checkout
210160 uses : actions/checkout@v7
211161 with :
212162 fetch-depth : 0
213-
163+
214164 - name : Setup .NET 10
215165 uses : actions/setup-dotnet@v6
216166 with :
@@ -229,14 +179,14 @@ jobs:
229179
230180 if [ "$is_packable" = "true" ] && [ -n "$package_id" ]; then
231181 projects+=("$project")
232- echo "✅ Packable: $project ($package_id)"
182+ echo "Packable: $project ($package_id)"
233183 else
234- echo "⏭️ Skipped: $project (IsPackable=$is_packable, PackageId=$package_id)"
184+ echo "Skipped: $project (IsPackable=$is_packable, PackageId=$package_id)"
235185 fi
236186 done < <(find ./src -name '*.csproj' -print0)
237187
238188 if [ ${#projects[@]} -eq 0 ]; then
239- echo "❌ No packable projects found under ./src"
189+ echo "No packable projects found under ./src"
240190 exit 1
241191 fi
242192
@@ -246,7 +196,7 @@ jobs:
246196 echo "EOF"
247197 } >> "$GITHUB_OUTPUT"
248198
249- echo "📦 Discovered ${#projects[@]} packable project(s)."
199+ echo "Discovered ${#projects[@]} packable project(s)."
250200
251201 - name : Pack NuGet packages
252202 shell : bash
@@ -257,15 +207,15 @@ jobs:
257207
258208 while IFS= read -r project; do
259209 [ -n "$project" ] || continue
260- echo "📦 Packing $project"
210+ echo "Packing $project"
261211 dotnet pack "$project" \
262212 --configuration Release \
263213 --output ./artifacts \
264214 /p:ContinuousIntegrationBuild=true \
265215 /p:UseLocalProjectReferences=true \
266216 --configfile NuGet.Config
267217 done <<< "${{ steps.discover-packable.outputs.packable_projects }}"
268-
218+
269219 - name : Upload artifacts
270220 uses : actions/upload-artifact@v4
271221 with :
@@ -278,19 +228,24 @@ jobs:
278228 runs-on : ubuntu-latest
279229 needs : pack
280230 if : github.event_name == 'push' && github.ref == 'refs/heads/master'
281-
231+
282232 steps :
233+ - name : Checkout
234+ uses : actions/checkout@v7
235+ with :
236+ fetch-depth : 0
237+
283238 - name : Setup .NET 10
284239 uses : actions/setup-dotnet@v6
285240 with :
286241 dotnet-version : ' 10.0.x'
287-
242+
288243 - name : Download NuGet packages
289244 uses : actions/download-artifact@v4
290245 with :
291246 name : nuget-packages
292247 path : ./artifacts
293-
248+
294249 - name : Publish packages in dependency order
295250 shell : bash
296251 env :
@@ -309,7 +264,7 @@ jobs:
309264 for pkg in ./artifacts/${pattern}; do
310265 [ -f "$pkg" ] || continue
311266 found_any=true
312- echo " ↗ Pushing $(basename "$pkg")"
267+ echo " Publishing $(basename "$pkg")"
313268 dotnet nuget push "$pkg" \
314269 --api-key "$NUGET_API_KEY" \
315270 --source "$NUGET_SOURCE" \
@@ -318,20 +273,17 @@ jobs:
318273 done
319274 echo "::endgroup::"
320275
321- # Wait for NuGet.org indexing before pushing the next layer
322276 if [ "$found_any" = true ]; then
323- echo "⏳ Waiting 30s for NuGet.org to index $layer_name..."
277+ echo "Waiting 30s for NuGet.org to index $layer_name..."
324278 sleep 30
325279 fi
326280 }
327281
328- # Layer 0 – no internal dependencies
329282 push_layer "Layer 0 (core)" \
330283 "SharpCoreDB.[0-9]*.nupkg" \
331284 "SharpCoreDB.Client.Protocol.*.nupkg" \
332285 "SharpCoreDB.Server.Protocol.*.nupkg"
333286
334- # Layer 1 – depends on core only
335287 push_layer "Layer 1 (core dependents)" \
336288 "SharpCoreDB.Graph.[0-9]*.nupkg" \
337289 "SharpCoreDB.VectorSearch.*.nupkg" \
@@ -342,7 +294,6 @@ jobs:
342294 "SharpCoreDB.Identity.*.nupkg" \
343295 "SharpCoreDB.Serilog.Sinks.*.nupkg"
344296
345- # Layer 2 – depends on layer 1
346297 push_layer "Layer 2 (mid-level)" \
347298 "SharpCoreDB.Client.[0-9]*.nupkg" \
348299 "SharpCoreDB.EntityFrameworkCore.[0-9]*.nupkg" \
@@ -352,7 +303,6 @@ jobs:
352303 "SharpCoreDB.CQRS.*.nupkg" \
353304 "SharpCoreDB.Projections.*.nupkg"
354305
355- # Layer 3 – depends on layer 2
356306 push_layer "Layer 3 (top-level)" \
357307 "SharpCoreDB.Server.[0-9]*.nupkg" \
358308 "SharpCoreDB.Graph.Advanced.*.nupkg" \
@@ -362,20 +312,16 @@ jobs:
362312 "SharpCoreDB.Functional.EntityFrameworkCore.*.nupkg" \
363313 "SharpCoreDB.Functional.Linq2DB.*.nupkg"
364314
365- # Catch any remaining packages not in the layers above
366315 push_layer "Remaining packages" \
367316 "*.nupkg"
368317
369- echo "✅ All packages published in dependency order."
318+ echo "All packages published in dependency order."
370319
371320 - name : Create release summary
372321 run : |
373- echo "## 📦 NuGet Packages Published (Dependency-Ordered)" >> $GITHUB_STEP_SUMMARY
322+ echo "## NuGet Packages Published (Dependency-Ordered)" >> $GITHUB_STEP_SUMMARY
374323 echo "" >> $GITHUB_STEP_SUMMARY
375324 echo "Published $(ls -1 ./artifacts/*.nupkg | wc -l) package(s) to NuGet.org" >> $GITHUB_STEP_SUMMARY
376325 echo "" >> $GITHUB_STEP_SUMMARY
377- echo "Packages were published in 4 layers based on their dependency chain," >> $GITHUB_STEP_SUMMARY
378- echo "with 30-second waits between layers for NuGet.org indexing." >> $GITHUB_STEP_SUMMARY
379- echo "" >> $GITHUB_STEP_SUMMARY
380326 echo "### Packages" >> $GITHUB_STEP_SUMMARY
381- ls -1 ./artifacts/*.nupkg | xargs -I {} basename {} >> $GITHUB_STEP_SUMMARY
327+ ls -1 ./artifacts/*.nupkg | xargs -I {} basename {} >> $GITHUB_STEP_SUMMARY
0 commit comments