the Apidiff
library can be used where you want to add a check to the public api surface of your library.
It's a library who can be used in tests, to check all api changes are expected, to not have unwanted changes in the api surface for consumers.
It make it easy for developers, with minimal maintenance, to be sure what consumer see and what api contract we may break with changes.
Scenarios:
- A new method is added, but for a mistake is public instead of private. Removing that from next version mean a breaking change, who can be avoided.
- A library expose a function and after a refactoring, the api slightly changes (a generic type become non generic or vice versa) and that mean is not compatible anymore, so a breaking change.
- What's the impact of the PR on the public api?
NOTE It doesnt enforce any semver convention, that's out of scope.
The public api surface of the library is written to a file committed in the repository, as baseline, who contains all the information about public types, methods, etc with full signature. Private/internal types/methods are ignored.
The ApiDiff.apiDiff
function is used in an test, where the new built assembly api is compared with the one committed in the repo.
If it's the same, the test pass.
Otherwise, the test fails showing the differences (like methods added or removed).
If that's expected, because the file is overridden when ApiDiff.apiDiff
is called, you just need to commit the changes of the file, the test will pass next run.
The api surface file for a source file like this is like
TYPE: Lib1.Duck
PROP: System.Tuple`2<System.Int32,System.Int32> Lib1.Duck::Position()
METHOD: System.Void Lib1.Duck::.ctor()
METHOD: System.String Lib1.Duck::Quack()
METHOD: System.Void Lib1.Duck::Walk()
TYPE: Lib1.Say
METHOD: System.Void Lib1.Say::hello(System.String)
TYPE: Lib1.Say/InItalian
METHOD: System.Void Lib1.Say/InItalian::ciao(System.String)
This format:
- It's concise, just the full signature
- All info is sorted by name (type, methods, etc), so order is deterministic and allow an easier diff
- Can be read by humans
NOTE the ApiDiff.apiDiff
use git diff
to show differences, in a style familiar for developers
NOTE git
is required in PATH
In the test project, create an helper functions like this
// assemblyPath is the full path to the assembly
// tfm is the target framework, because there is one baseline file for each target framework
let checkApi assemblyPath tfm =
printfn "api check for tfm %s of assembly %s" tfm assemblyPath
// the path for baseline file, in same lib, one file per target framework
let outputPath = Path.Combine(__SOURCE_DIRECTORY__, sprintf "MyLib.%s.txt" tfm)
// run the diff
match ApiDiff.apiDiff assemblyPath outputPath with
| Choice1Of2 () ->
() // no diff, ok
| Choice2Of2 diffLines -> //failure, diff lines contains the difference
diffLines
|> Seq.iter (printfn "%s")
Assert.True(false, sprintf "the %s api of '%s' doesnt match, see git diff of '%s' " tfm assemblyPath outputPath)
and in a test, add one test for each target framework, like
#if NETCOREAPP2_0
[<Fact>]
member __.``netstandard2.0 api`` () =
let assemblyPath = typeof<MyPublicType>.Assembly.Location
checkApi assemblyPath "netstandard2.0"
#endif
#if NET461
[<Fact>]
member __.``net461 api`` () =
let assemblyPath = typeof<MyPublicType>.Assembly.Location
checkApi assemblyPath "net45"
#endif
That's because the test frameworks usually run multiple times, one for each target framework.
We reuse this behaviour, to get the path of the referenced assembly under test who contains the public MyPublicType
type.
NOTE the correct test it's executed in the right target framework, because it's under a compiler define (the #ifdef NET461
)
NOTE The helper function is not included in the library OOTB, because test frameworks may differ in the logging api (like ITestOutputHelper
for XUnit vs Console.Writeline
) and the assert library.
This repository is actively monitored by Jet Engineers and the Jet Security team. Please monitor this repo for security updates and advisories. For more information and contacts, please see SECURITY