diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c5b2f6e3cb1..0f067ac4d77 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -494,6 +494,40 @@ jobs:
test/netstd/Server/bin/Release/
retention-days: 3
+ lib-netstd-codegen:
+ needs: [compiler]
+ runs-on: ubuntu-24.04
+ strategy:
+ matrix:
+ dotnet-version: [8, 9, 10]
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+
+ - name: Set up .NET SDK ${{ matrix.dotnet-version }}
+ uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
+ with:
+ dotnet-version: "${{ matrix.dotnet-version }}.0.x"
+
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: thrift-compiler
+ path: compiler/cpp
+
+ - name: Run thrift-compiler
+ run: |
+ chmod a+x compiler/cpp/thrift
+ echo "$GITHUB_WORKSPACE/compiler/cpp" >> "$GITHUB_PATH"
+ compiler/cpp/thrift -version
+
+ - name: Run netstd codegen tests
+ shell: pwsh
+ run: |
+ lib/netstd/Tests/codegen/run-NetStd-Codegen-Tests.ps1 `
+ -TargetVersions @("net${{ matrix.dotnet-version }}")
+
lib-haxe-codegen:
needs: [compiler]
runs-on: ubuntu-24.04
diff --git a/lib/netstd/Tests/codegen/run-NetStd-Codegen-Tests.ps1 b/lib/netstd/Tests/codegen/run-NetStd-Codegen-Tests.ps1
new file mode 100644
index 00000000000..a0ea788b04a
--- /dev/null
+++ b/lib/netstd/Tests/codegen/run-NetStd-Codegen-Tests.ps1
@@ -0,0 +1,311 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+
+param(
+ [string[]] $TargetVersions = @(), # override NET_VERSIONS; when empty all defaults are tested
+ [string] $TargetFolder = "", # output dir; when empty uses system temp
+ [string] $ExtraThriftFiles = "" # additional .thrift search path; when empty none are added
+)
+
+#---- failing tests --------------------------------------------
+
+# expected to fail at Thrift Compiler
+$FAIL_THRIFT = @(
+ "BrokenConstants.thrift", # intended to break
+ "Buses.thrift", # subdir includes don't work here
+ "DuplicateImportsTest.thrift", # subdir includes don't work here
+ "Include.thrift", # subdir includes don't work here
+ "MaintenanceFacility.thrift", # subdir includes don't work here
+ "Transporters.thrift") # subdir includes don't work here
+
+# expected to fail at net Compiler
+$FAIL_DOTNET = @(
+)
+
+# unexpected but known bugs (TODO: fix them)
+$KNOWN_BUGS = @(
+ )
+
+$NET_VERSIONS = @(
+ "net8",
+ "net9",
+ "net10"
+)
+if ($TargetVersions.Count -gt 0) { $NET_VERSIONS = $TargetVersions }
+
+
+#---- consts --------------------------------------------
+
+$EXE_EXT = ($OsType -eq "Windows") ? ".exe" : ""
+
+
+#---- functions --------------------------------------------
+
+function FindThriftExe() {
+ # prefer debug over release over path
+ $exe = "thrift" + $EXE_EXT
+ write-host -nonewline Looking for $exe ...
+
+ # if we have a freshly compiled one it might be a better choice
+ @("Release","Debug") | foreach{
+ if( test-path ([System.IO.Path]::Combine($ROOTDIR,"compiler","cpp",$_,$exe))) { $exe = [System.IO.Path]::Combine($ROOTDIR,"compiler","cpp",$_,$exe) }
+ if( test-path ([System.IO.Path]::Combine($ROOTDIR,"compiler","cpp","compiler",$_,$exe))) { $exe = [System.IO.Path]::Combine($ROOTDIR,"compiler","cpp",$_,"compiler",$exe) }
+ }
+ # cmake build outputs take highest priority (Linux/macOS and cmake on Windows)
+ @("cmake_build","cmake_build_compiler") | foreach{
+ $candidate = [System.IO.Path]::Combine($ROOTDIR, $_, "compiler", "cpp", "bin", $exe)
+ if( test-path $candidate) { $exe = $candidate }
+ }
+
+ return $exe
+}
+
+
+function FindDotnet() {
+ # prefer debug over release over path
+ $exe = "dotnet" + $EXE_EXT
+ write-host -nonewline Looking for $exe ...
+
+ # TODO: add arbitraily complex code to locate a suitable dotnet if it is not in the path
+
+ return $exe
+}
+
+
+function InitializeFolder([string] $folder, [string] $pattern) {
+ #write-host $folder\$pattern
+ if(-not (test-path $folder)) {
+ new-item $folder -type directory | out-null
+ }
+ pushd $folder
+ gci . -include $pattern -recurse | foreach{ remove-item $_ }
+ popd
+}
+
+
+function CopyFilesFrom([string] $source, $text) {
+ #write-host "$source"
+ $counter = 0
+ if( ($source -ne "") -and (test-path $source)) {
+ if( $text -ne $null) {
+ write-host -nonewline -foregroundcolor yellow Copying $text ...
+ }
+
+ pushd $source
+ # recurse dirs
+ gci . -directory | foreach {
+ $counter += CopyFilesFrom "$_"
+ }
+ # files within
+ gci *.thrift -file | foreach {
+ #write-host $_
+ $name = $_.name
+ copy-item $_ ([System.IO.Path]::Combine($TARGET, $name))
+ $counter++
+ }
+ popd
+
+ if( $text -ne $null) {
+ write-host -foregroundcolor yellow $counter files
+ }
+ }
+ return $counter
+}
+
+function TestIdlFile([string] $idl) {
+ # expected to fail at Thrift Compiler
+ $filter = $false
+ $FAIL_THRIFT | foreach {
+ if( $idl -eq $_) {
+ $filter = $true
+ write-host "Skipping $_"
+ }
+ }
+ if( $filter) { return $true }
+
+ # compile IDL
+ #write-host -nonewline " $idl"
+ $gendir = [System.IO.Path]::Combine($TARGET, "gen-netstd")
+ $thrift_csproj = [System.IO.Path]::Combine($ROOTDIR, "lib", "netstd", "Thrift", "Thrift.csproj")
+ InitializeFolder $gendir "*.cs"
+ &$THRIFT_EXE $VERBOSE -r --gen "netstd:$net_version" $idl | out-file ([System.IO.Path]::Combine($TARGET, "thrift.log"))
+ if( -not $?) {
+ get-content ([System.IO.Path]::Combine($TARGET, "thrift.log")) | out-default
+ write-host -foregroundcolor red "Thrift compilation failed: $idl"
+ return $false
+ }
+
+ # generate solution
+ $slnxfile = [System.IO.Path]::Combine($gendir, "$TESTAPP.slnx")
+ if( -not (test-path $slnxfile)) {
+ $lines = @()
+ $lines += ""
+ $lines += " "
+ $lines += " "
+ $lines += ""
+ $lines | set-content $slnxfile
+ }
+
+ # generate program main - always, because of $net_version
+ $lines = @()
+ $lines += ""
+ $lines += ""
+ $lines += " "
+ $lines += " Exe"
+ $lines += " $net_version.0"
+ $lines += " enable"
+ $lines += " "
+ $lines += ""
+ $lines += " "
+ $lines += " "
+ $lines += " "
+ $lines += ""
+ $lines += ""
+ $lines += ""
+ $lines | set-content ([System.IO.Path]::Combine($gendir, "$TESTAPP.csproj"))
+
+ # generate project file
+ $testcs = [System.IO.Path]::Combine($gendir, "$TESTAPP.cs")
+ if( -not (test-path $testcs)) {
+ $lines = @()
+ $lines += "namespace $TESTAPP"
+ $lines += "{"
+ $lines += " internal class Program"
+ $lines += " {"
+ $lines += " static void Main(string[] args)"
+ $lines += " {"
+ $lines += " System.Console.WriteLine(`"Hello, World!`");"
+ $lines += " }"
+ $lines += " }"
+ $lines += "}"
+ $lines | set-content $testcs
+ }
+
+ # try to compile the program
+ # this should not throw any errors, warnings or hints
+ $exe = [System.IO.Path]::Combine($gendir, "bin", "Debug", "$net_version.0", $TESTAPP + $EXE_EXT)
+ if( test-path $exe) { remove-item $exe }
+ $compilelog = [System.IO.Path]::Combine($TARGET, "compile.log")
+ &$DOTNET_EXE build $slnxfile | out-file $compilelog
+ if( -not (test-path $exe)) {
+
+ # expected to fail at Thrift Compiler
+ $filter = $false
+ $FAIL_DOTNET | foreach {
+ if( $idl -eq $_) {
+ $filter = $true
+ write-host ("C# compilation failed at "+$idl+" - as expected")
+ }
+ }
+ $KNOWN_BUGS | foreach {
+ if( $idl -eq $_) {
+ $filter = $true
+ write-host ("C# compilation failed at "+$idl+" - known issue (TODO)")
+ }
+ }
+ if( $filter) { return $true }
+
+ get-content $compilelog | out-default
+ write-host -foregroundcolor red "C# compilation failed: $idl"
+ return $false
+ }
+
+ # The compiled program is now executed. If it hangs or crashes, we
+ # have a serious problem with the generated code.
+ $execlog = [System.IO.Path]::Combine($TARGET, "exec.log")
+ &"$exe" | out-file $execlog
+ if( -not $?) {
+ get-content $execlog | out-default
+ write-host -foregroundcolor red "Test execution failed: $idl"
+ return $false
+ }
+ return $true
+}
+
+#---- main -------------------------------------------------
+# CONFIGURATION BEGIN
+# configuration settings, adjust as necessary to meet your system setup
+$MY_THRIFT_FILES = $ExtraThriftFiles
+$VERBOSE = "" # set any Thrift compiler debug/verbose flag you want
+
+# init
+$ROOTDIR = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, '..', '..', '..', '..'))
+
+# try to find thrift
+$THRIFT_EXE = FindThriftExe
+&$THRIFT_EXE -version
+if( -not $?) {
+ write-host -foregroundcolor red ("Missing thrift" + $EXE_EXT)
+ exit 1
+}
+
+# try to find dotnet
+$DOTNET_EXE = FindDotnet
+&$DOTNET_EXE --version
+if( -not $?) {
+ write-host -foregroundcolor red ("Missing dotnet" + $EXE_EXT)
+ exit 1
+}
+
+
+# some helpers
+if ($TargetFolder -ne "") {
+ $TARGET = $TargetFolder
+} else {
+ $TARGET = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "thrift-testing")
+}
+$TESTAPP = "TestProject"
+
+# create and/or empty target dirs
+InitializeFolder $TARGET "*.thrift"
+InitializeFolder ([System.IO.Path]::Combine($TARGET, "gen-netstd")) "*.cs"
+
+# recurse through thrift WC and "my thrift files" folder
+# copies all .thrift files into thrift-testing
+CopyFilesFrom "$ROOTDIR" "Thrift IDL files" | out-null
+CopyFilesFrom "$MY_THRIFT_FILES" "Custom IDL files" | out-null
+
+# codegen and compile all thrift files, one by one to prevent side effects
+$count = 0
+write-host -foregroundcolor yellow Running codegen tests ..
+try {
+ pushd "$TARGET"
+ $NET_VERSIONS | foreach{
+ $net_version = $_
+ write-host -foregroundcolor cyan Targeting $net_version
+
+ gci *.thrift -file | foreach {
+ $count += 1
+ $ok = TestIdlFile $_.name
+ if( -not $ok) {
+ throw "TEST FAILED" # automated tests
+ popd; pause; pushd "$TARGET" # interactive / debug
+ }
+ }
+ }
+ write-host -foregroundcolor green "Success ($count tests executed)"
+ exit 0
+} catch {
+ write-host -foregroundcolor red $_
+ exit 1
+} finally {
+ popd
+}
+
+#eof