| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package apidiff |
| |
| import ( |
| "encoding/json" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen" |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgentest" |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/summarize" |
| ) |
| |
| var ( |
| cmpOptions = cmpopts.IgnoreUnexported(Report{}) |
| ) |
| |
| func TestApiDiff(t *testing.T) { |
| t.Parallel() |
| tests := []struct { |
| name string |
| before string |
| after string |
| expected string |
| }{ |
| // library |
| { |
| name: "library 1", |
| before: ` |
| library l; |
| `, |
| after: ` |
| library l; |
| `, |
| expected: ` |
| {} |
| `, |
| }, |
| { |
| name: "library 2", |
| before: ` |
| library l1; |
| `, |
| after: ` |
| library l2; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l1", |
| "before": { |
| "kind": "library", |
| "name": "l1" |
| }, |
| "conclusion": "APIBreaking" |
| }, |
| { |
| "name": "l2", |
| "after": { |
| "kind": "library", |
| "name": "l2" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| |
| // const |
| { |
| name: "const", |
| before: ` |
| library l; |
| const FOO int32 = 32; |
| `, |
| after: ` |
| library l; |
| const FOO int32 = 32; |
| `, |
| expected: ` |
| {} |
| `, |
| }, |
| { |
| name: "const 2", |
| before: ` |
| library l; |
| const FOO int32 = 32; |
| `, |
| after: ` |
| library l; |
| const FOO string = "fuzzy"; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/FOO", |
| "before": { |
| "kind": "const", |
| "name": "l/FOO", |
| "type": "int32", |
| "value": "32" |
| }, |
| "after": { |
| "kind": "const", |
| "name": "l/FOO", |
| "type": "string", |
| "value": "fuzzy" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "const remove", |
| before: ` |
| library l; |
| const FOO int32 = 32; |
| `, |
| after: ` |
| library l; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/FOO", |
| "before": { |
| "kind": "const", |
| "name": "l/FOO", |
| "type": "int32", |
| "value": "32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "const add", |
| before: ` |
| library l; |
| `, |
| after: ` |
| library l; |
| const FOO string = "fuzzy"; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/FOO", |
| "after": { |
| "kind": "const", |
| "name": "l/FOO", |
| "type": "string", |
| "value": "fuzzy" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "const value change", |
| before: ` |
| library l; |
| const FOO int32 = 32; |
| `, |
| after: ` |
| library l; |
| const FOO int32 = 42; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/FOO", |
| "before": { |
| "kind": "const", |
| "name": "l/FOO", |
| "type": "int32", |
| "value": "32" |
| }, |
| "after": { |
| "kind": "const", |
| "name": "l/FOO", |
| "type": "int32", |
| "value": "42" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| |
| // bits |
| { |
| name: "bits member add to flexible", |
| before: ` |
| library l; |
| type Bits = flexible bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| after: ` |
| library l; |
| type Bits = flexible bits { |
| BIT1 = 0x01; |
| BIT2 = 0x02; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits.BIT2", |
| "after": { |
| "kind": "bits/member", |
| "name": "l/Bits.BIT2", |
| "value": "2" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits member add to strict", |
| before: ` |
| library l; |
| type Bits = strict bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| after: ` |
| library l; |
| type Bits = strict bits { |
| BIT1 = 0x01; |
| BIT2 = 0x02; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits.BIT2", |
| "after": { |
| "kind": "bits/member", |
| "name": "l/Bits.BIT2", |
| "value": "2" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits member remove", |
| before: ` |
| library l; |
| type Bits = bits { |
| BIT1 = 0x01; |
| BIT2 = 0x02; |
| }; |
| `, |
| after: ` |
| library l; |
| type Bits = bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits.BIT2", |
| "before": { |
| "kind": "bits/member", |
| "name": "l/Bits.BIT2", |
| "value": "2" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits add", |
| before: ` |
| library l; |
| `, |
| after: ` |
| library l; |
| type Bits = strict bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits.BIT1", |
| "after": { |
| "kind": "bits/member", |
| "name": "l/Bits.BIT1", |
| "value": "1" |
| }, |
| "conclusion": "Compatible" |
| }, |
| { |
| "name": "l/Bits", |
| "after": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits remove", |
| before: ` |
| library l; |
| type Bits = strict bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| after: ` |
| library l; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits.BIT1", |
| "before": { |
| "kind": "bits/member", |
| "name": "l/Bits.BIT1", |
| "value": "1" |
| }, |
| "conclusion": "APIBreaking" |
| }, |
| { |
| "name": "l/Bits", |
| "before": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits make flexible", |
| before: ` |
| library l; |
| type Bits = strict bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| after: ` |
| library l; |
| type Bits = flexible bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits", |
| "before": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "after": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "flexible", |
| "type": "uint32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits make strict", |
| before: ` |
| library l; |
| type Bits = flexible bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| after: ` |
| library l; |
| type Bits = strict bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits", |
| "before": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "flexible", |
| "type": "uint32" |
| }, |
| "after": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits change underlying type", |
| before: ` |
| library l; |
| type Bits = strict bits : uint32 { |
| BIT1 = 0x01; |
| }; |
| `, |
| after: ` |
| library l; |
| type Bits = strict bits : uint8 { |
| BIT1 = 0x01; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits", |
| "before": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "after": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "strict", |
| "type": "uint8" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits underlying type change", |
| before: ` |
| library l; |
| type Bits = strict bits : uint32 { |
| BIT1 = 0x01; |
| }; |
| `, |
| after: ` |
| library l; |
| type Bits = strict bits : uint8 { |
| BIT1 = 0x01; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits", |
| "before": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "after": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "strict", |
| "type": "uint8" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits change type and strictness to flexible", |
| before: ` |
| library l; |
| type Bits = strict bits : uint32 { |
| BIT1 = 0x01; |
| }; |
| `, |
| after: ` |
| library l; |
| type Bits = flexible bits : uint8 { |
| BIT1 = 0x01; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits", |
| "before": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "after": { |
| "kind": "bits", |
| "name": "l/Bits", |
| "strictness": "flexible", |
| "type": "uint8" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "bits value change", |
| before: ` |
| library l; |
| type Bits = flexible bits { |
| BIT1 = 0x01; |
| }; |
| `, |
| after: ` |
| library l; |
| type Bits = flexible bits { |
| BIT1 = 0x02; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Bits.BIT1", |
| "before": { |
| "kind": "bits/member", |
| "name": "l/Bits.BIT1", |
| "value": "1" |
| }, |
| "after": { |
| "kind": "bits/member", |
| "name": "l/Bits.BIT1", |
| "value": "2" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| |
| // enum |
| { |
| name: "enum add", |
| before: ` |
| library l; |
| `, |
| after: ` |
| library l; |
| type Enum = strict enum { |
| WATER = 1; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Enum.WATER", |
| "after": { |
| "kind": "enum/member", |
| "name": "l/Enum.WATER", |
| "value": "1" |
| }, |
| "conclusion": "Compatible" |
| }, |
| { |
| "name": "l/Enum", |
| "after": { |
| "kind": "enum", |
| "name": "l/Enum", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "enum value add to flexible", |
| before: ` |
| library l; |
| type Enum = flexible enum { |
| WATER = 1; |
| }; |
| `, |
| after: ` |
| library l; |
| type Enum = flexible enum { |
| WATER = 1; |
| FIRE = 2; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Enum.FIRE", |
| "after": { |
| "kind": "enum/member", |
| "name": "l/Enum.FIRE", |
| "value": "2" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "enum value add to strict", |
| before: ` |
| library l; |
| type Enum = strict enum { |
| WATER = 1; |
| }; |
| `, |
| after: ` |
| library l; |
| type Enum = strict enum { |
| WATER = 1; |
| FIRE = 2; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Enum.FIRE", |
| "after": { |
| "kind": "enum/member", |
| "name": "l/Enum.FIRE", |
| "value": "2" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "enum value remove", |
| before: ` |
| library l; |
| type Enum = enum { |
| WATER = 1; |
| FIRE = 2; |
| }; |
| `, |
| after: ` |
| library l; |
| type Enum = enum { |
| WATER = 1; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Enum.FIRE", |
| "before": { |
| "kind": "enum/member", |
| "name": "l/Enum.FIRE", |
| "value": "2" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "enum strictness change to flexible", |
| before: ` |
| library l; |
| type Enum = strict enum { |
| WATER = 1; |
| }; |
| `, |
| after: ` |
| library l; |
| type Enum = flexible enum { |
| WATER = 1; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Enum", |
| "before": { |
| "kind": "enum", |
| "name": "l/Enum", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "after": { |
| "kind": "enum", |
| "name": "l/Enum", |
| "strictness": "flexible", |
| "type": "uint32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "enum strictness change to strict", |
| before: ` |
| library l; |
| type Enum = flexible enum { |
| WATER = 1; |
| }; |
| `, |
| after: ` |
| library l; |
| type Enum = strict enum { |
| WATER = 1; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Enum", |
| "before": { |
| "kind": "enum", |
| "name": "l/Enum", |
| "strictness": "flexible", |
| "type": "uint32" |
| }, |
| "after": { |
| "kind": "enum", |
| "name": "l/Enum", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "enum underlying type change", |
| before: ` |
| library l; |
| type Enum = strict enum : uint32 { |
| WATER = 1; |
| }; |
| `, |
| after: ` |
| library l; |
| type Enum = strict enum : uint8 { |
| WATER = 1; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Enum", |
| "before": { |
| "kind": "enum", |
| "name": "l/Enum", |
| "strictness": "strict", |
| "type": "uint32" |
| }, |
| "after": { |
| "kind": "enum", |
| "name": "l/Enum", |
| "strictness": "strict", |
| "type": "uint8" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "enum value change", |
| before: ` |
| library l; |
| type Enum = enum { |
| WATER = 1; |
| }; |
| `, |
| after: ` |
| library l; |
| type Enum = enum { |
| WATER = 2; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Enum.WATER", |
| "before": { |
| "kind": "enum/member", |
| "name": "l/Enum.WATER", |
| "value": "1" |
| }, |
| "after": { |
| "kind": "enum/member", |
| "name": "l/Enum.WATER", |
| "value": "2" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| |
| // struct |
| { |
| name: "struct add", |
| before: ` |
| library l; |
| `, |
| after: ` |
| library l; |
| type Struct = struct {}; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct", |
| "after": { |
| "kind": "struct", |
| "name": "l/Struct" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "struct remove", |
| before: ` |
| library l; |
| type Struct = struct {}; |
| `, |
| after: ` |
| library l; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct", |
| "before": { |
| "kind": "struct", |
| "name": "l/Struct" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "struct become resource", |
| before: ` |
| library l; |
| type Struct = struct {}; |
| `, |
| after: ` |
| library l; |
| type Struct = resource struct {}; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct", |
| "before": { |
| "kind": "struct", |
| "name": "l/Struct" |
| }, |
| "after": { |
| "kind": "struct", |
| "name": "l/Struct", |
| "resourceness": "resource" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "struct unbecome resource", |
| before: ` |
| library l; |
| type Struct = resource struct {}; |
| `, |
| after: ` |
| library l; |
| type Struct = struct {}; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct", |
| "before": { |
| "kind": "struct", |
| "name": "l/Struct", |
| "resourceness": "resource" |
| }, |
| "after": { |
| "kind": "struct", |
| "name": "l/Struct" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| // The addition of the member should not be considered ABI breaking |
| // because it is added at the same time as the struct. |
| name: "struct with member add", |
| after: ` |
| library l; |
| type Struct = struct { |
| member int32; |
| }; |
| `, |
| before: ` |
| library l; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct.member", |
| "after": { |
| "kind": "struct/member", |
| "name": "l/Struct.member", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "conclusion": "Compatible" |
| }, |
| { |
| "name": "l/Struct", |
| "after": { |
| "kind": "struct", |
| "name": "l/Struct" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "struct/member add", |
| before: ` |
| library l; |
| type Struct = struct {}; |
| `, |
| after: ` |
| library l; |
| type Struct = struct { |
| foo int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct.foo", |
| "after": { |
| "kind": "struct/member", |
| "name": "l/Struct.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "struct remove member", |
| before: ` |
| library l; |
| type Struct = struct { |
| foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type Struct = struct { |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct.foo", |
| "before": { |
| "kind": "struct/member", |
| "name": "l/Struct.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "struct change type", |
| before: ` |
| library l; |
| type Struct = struct { |
| foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type Struct = struct { |
| foo string; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct.foo", |
| "before": { |
| "kind": "struct/member", |
| "name": "l/Struct.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "after": { |
| "kind": "struct/member", |
| "name": "l/Struct.foo", |
| "ordinal": "1", |
| "type": "string" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "struct default value change", |
| before: ` |
| library l; |
| type Struct = struct { |
| @allow_deprecated_struct_defaults |
| foo int32 = 1; |
| }; |
| `, |
| after: ` |
| library l; |
| type Struct = struct { |
| @allow_deprecated_struct_defaults |
| foo int32 = 2; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct.foo", |
| "before": { |
| "kind": "struct/member", |
| "name": "l/Struct.foo", |
| "ordinal": "1", |
| "type": "int32", |
| "value": "1" |
| }, |
| "after": { |
| "kind": "struct/member", |
| "name": "l/Struct.foo", |
| "ordinal": "1", |
| "type": "int32", |
| "value": "2" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "struct reorder member", |
| before: ` |
| library l; |
| type Struct = struct { |
| foo int32; |
| bar string; |
| }; |
| `, |
| after: ` |
| library l; |
| type Struct = struct { |
| bar string; |
| foo int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/Struct.bar", |
| "before": { |
| "kind": "struct/member", |
| "name": "l/Struct.bar", |
| "ordinal": "2", |
| "type": "string", |
| "value": "" |
| }, |
| "after": { |
| "kind": "struct/member", |
| "name": "l/Struct.bar", |
| "ordinal": "1", |
| "type": "string", |
| "value": "" |
| }, |
| "conclusion": "APIBreaking" |
| }, |
| { |
| "name": "l/Struct.foo", |
| "before": { |
| "kind": "struct/member", |
| "name": "l/Struct.foo", |
| "ordinal": "1", |
| "type": "int32", |
| "value": "" |
| }, |
| "after": { |
| "kind": "struct/member", |
| "name": "l/Struct.foo", |
| "ordinal": "2", |
| "type": "int32", |
| "value": "" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| |
| // table |
| { |
| name: "table add", |
| before: ` |
| library l; |
| `, |
| after: ` |
| library l; |
| type T = table {}; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "after": { |
| "kind": "table", |
| "name": "l/T" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "table remove", |
| before: ` |
| library l; |
| type T = table {}; |
| `, |
| after: ` |
| library l; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "before": { |
| "kind": "table", |
| "name": "l/T" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "table become resource", |
| before: ` |
| library l; |
| type T = table {}; |
| `, |
| after: ` |
| library l; |
| type T = resource table {}; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "before": { |
| "kind": "table", |
| "name": "l/T" |
| }, |
| "after": { |
| "kind": "table", |
| "name": "l/T", |
| "resourceness": "resource" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "table unbecome resource", |
| before: ` |
| library l; |
| type T = resource table {}; |
| `, |
| after: ` |
| library l; |
| type T = table {}; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "before": { |
| "kind": "table", |
| "name": "l/T", |
| "resourceness": "resource" |
| }, |
| "after": { |
| "kind": "table", |
| "name": "l/T" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "table add member", |
| before: ` |
| library l; |
| type T = table {}; |
| `, |
| after: ` |
| library l; |
| type T = table { |
| 1: foo int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "after": { |
| "kind": "table/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "table remove member", |
| before: ` |
| library l; |
| type T = table { |
| 1: foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = table { |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "before": { |
| "kind": "table/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "table change type", |
| before: ` |
| library l; |
| type T = table { |
| 1: foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = table { |
| 1: foo string; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "before": { |
| "kind": "table/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "after": { |
| "kind": "table/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "string" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "table change ordinal", |
| before: ` |
| library l; |
| type T = table { |
| 1: foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = table { |
| 2: foo int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "before": { |
| "kind": "table/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "after": { |
| "kind": "table/member", |
| "name": "l/T.foo", |
| "ordinal": "2", |
| "type": "int32" |
| }, |
| "conclusion": "APICompatibleButABIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| |
| // union |
| { |
| name: "union add", |
| before: ` |
| library l; |
| `, |
| after: ` |
| library l; |
| type T = strict union { |
| 1: foo int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "after": { |
| "kind": "union/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "conclusion": "Compatible" |
| }, |
| { |
| "name": "l/T", |
| "after": { |
| "kind": "union", |
| "name": "l/T", |
| "strictness": "strict" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "union remove", |
| before: ` |
| library l; |
| type T = strict union { |
| 1: foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "before": { |
| "kind": "union/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "conclusion": "APIBreaking" |
| }, |
| { |
| "name": "l/T", |
| "before": { |
| "kind": "union", |
| "name": "l/T", |
| "strictness": "strict" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "union become resource", |
| before: ` |
| library l; |
| type T = strict union { |
| 1: foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = strict resource union { |
| 1: foo int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "before": { |
| "kind": "union", |
| "name": "l/T", |
| "strictness": "strict" |
| }, |
| "after": { |
| "kind": "union", |
| "name": "l/T", |
| "resourceness": "resource", |
| "strictness": "strict" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "union unbecome resource", |
| before: ` |
| library l; |
| type T = strict resource union { |
| 1: bar int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = strict union { |
| 1: bar int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "before": { |
| "kind": "union", |
| "name": "l/T", |
| "resourceness": "resource", |
| "strictness": "strict" |
| }, |
| "after": { |
| "kind": "union", |
| "name": "l/T", |
| "strictness": "strict" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "flexible union add member", |
| before: ` |
| library l; |
| type T = flexible union { |
| 1: bar int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = flexible union { |
| 1: bar int32; |
| 2: foo int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "after": { |
| "kind": "union/member", |
| "name": "l/T.foo", |
| "ordinal": "2", |
| "type": "int32" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "strict union add member", |
| before: ` |
| library l; |
| type T = strict union { |
| 1: bar int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = strict union { |
| 1: bar int32; |
| 2: foo int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "after": { |
| "kind": "union/member", |
| "name": "l/T.foo", |
| "ordinal": "2", |
| "type": "int32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "strict union remove member", |
| before: ` |
| library l; |
| type T = strict union { |
| 1: bar int32; |
| 2: foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = strict union { |
| 1: bar int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "before": { |
| "kind": "union/member", |
| "name": "l/T.foo", |
| "ordinal": "2", |
| "type": "int32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "union change type", |
| before: ` |
| library l; |
| type T = union { |
| 1: foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = union { |
| 1: foo string; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "before": { |
| "kind": "union/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "after": { |
| "kind": "union/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "string" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "union change ordinal", |
| before: ` |
| library l; |
| type T = union { |
| 1: foo int32; |
| }; |
| `, |
| after: ` |
| library l; |
| type T = union { |
| 2: foo int32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.foo", |
| "before": { |
| "kind": "union/member", |
| "name": "l/T.foo", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "after": { |
| "kind": "union/member", |
| "name": "l/T.foo", |
| "ordinal": "2", |
| "type": "int32" |
| }, |
| "conclusion": "APICompatibleButABIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| |
| // protocol |
| { |
| name: "protocol add", |
| before: ` |
| library l; |
| `, |
| after: ` |
| library l; |
| closed protocol T {}; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "after": { |
| "kind": "protocol", |
| "name": "l/T", |
| "openness": "closed", |
| "transport": "channel" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol remove", |
| before: ` |
| library l; |
| closed protocol T {}; |
| `, |
| after: ` |
| library l; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "before": { |
| "kind": "protocol", |
| "name": "l/T", |
| "openness": "closed", |
| "transport": "channel" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol change openness", |
| before: ` |
| library l; |
| closed protocol T {}; |
| `, |
| after: ` |
| library l; |
| ajar protocol T {}; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "before": { |
| "kind": "protocol", |
| "name": "l/T", |
| "openness": "closed", |
| "transport": "channel" |
| }, |
| "after": { |
| "kind": "protocol", |
| "name": "l/T", |
| "openness": "ajar", |
| "transport": "channel" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol change transport", |
| before: ` |
| library l; |
| closed protocol T {}; |
| `, |
| after: ` |
| library l; |
| @transport("Driver") |
| closed protocol T {}; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T", |
| "before": { |
| "kind": "protocol", |
| "name": "l/T", |
| "openness": "closed", |
| "transport": "channel" |
| }, |
| "after": { |
| "kind": "protocol", |
| "name": "l/T", |
| "openness": "closed", |
| "transport": "driver" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member add", |
| before: ` |
| library l; |
| closed protocol T { |
| }; |
| `, |
| after: ` |
| library l; |
| closed protocol T { |
| strict Test(struct { t int32; }) -> (); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way", |
| "request": "l/TTestRequest" |
| }, |
| "conclusion": "Compatible" |
| }, |
| { |
| "name": "l/TTestRequest.t", |
| "after": { |
| "kind": "struct/member", |
| "name": "l/TTestRequest.t", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "conclusion": "Compatible" |
| }, |
| { |
| "name": "l/TTestRequest", |
| "after": { |
| "kind": "struct", |
| "name": "l/TTestRequest" |
| }, |
| "conclusion": "Compatible" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member remove", |
| before: ` |
| library l; |
| closed protocol T { |
| strict Test(struct { t int32; }) -> (); |
| }; |
| `, |
| after: ` |
| library l; |
| closed protocol T { |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way", |
| "request": "l/TTestRequest" |
| }, |
| "conclusion": "APIBreaking" |
| }, |
| { |
| "name": "l/TTestRequest.t", |
| "before": { |
| "kind": "struct/member", |
| "name": "l/TTestRequest.t", |
| "ordinal": "1", |
| "type": "int32" |
| }, |
| "conclusion": "APIBreaking" |
| }, |
| { |
| "name": "l/TTestRequest", |
| "before": { |
| "kind": "struct", |
| "name": "l/TTestRequest" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member ordinal change", |
| before: ` |
| library l; |
| closed protocol T { |
| strict Test(struct { t int32; }) -> (); |
| }; |
| `, |
| after: ` |
| library l; |
| closed protocol T { |
| @selector("notl/NotT.NotTest") |
| strict Test(struct { t int32; }) -> (); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way", |
| "request": "l/TTestRequest" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "8693951483982195746", |
| "direction": "two_way", |
| "request": "l/TTestRequest" |
| }, |
| "conclusion": "APICompatibleButABIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member direction change", |
| before: ` |
| library l; |
| closed protocol T { |
| strict Test(); |
| }; |
| `, |
| after: ` |
| library l; |
| closed protocol T { |
| strict -> Test(); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "one_way" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "event" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member strictness change to flexible", |
| before: ` |
| library l; |
| open protocol T { |
| strict Test() -> (); |
| }; |
| `, |
| after: ` |
| library l; |
| open protocol T { |
| flexible Test() -> (); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "flexible", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way", |
| "response": "l/T_Test_Response" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member strictness change to strict", |
| before: ` |
| library l; |
| open protocol T { |
| flexible Test() -> (); |
| }; |
| `, |
| after: ` |
| library l; |
| open protocol T { |
| strict Test() -> (); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "flexible", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way", |
| "response": "l/T_Test_Response" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member request add", |
| before: ` |
| library l; |
| type R = struct { t int32; }; |
| closed protocol T { |
| strict Test(); |
| }; |
| `, |
| after: ` |
| library l; |
| type R = struct { t int32; }; |
| closed protocol T { |
| strict Test(R); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "one_way" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "one_way", |
| "request": "l/R" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member request remove", |
| before: ` |
| library l; |
| type R = struct { t int32; }; |
| closed protocol T { |
| strict Test(R); |
| }; |
| `, |
| after: ` |
| library l; |
| type R = struct { t int32; }; |
| closed protocol T { |
| strict Test(); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "one_way", |
| "request": "l/R" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "one_way" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member response add", |
| before: ` |
| library l; |
| type R = struct { t int32; }; |
| closed protocol T { |
| strict Test() -> (); |
| }; |
| `, |
| after: ` |
| library l; |
| type R = struct { t int32; }; |
| closed protocol T { |
| strict Test() -> (R); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way", |
| "response": "l/R" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member response remove", |
| before: ` |
| library l; |
| type R = struct { t int32; }; |
| closed protocol T { |
| strict Test() -> (R); |
| }; |
| `, |
| after: ` |
| library l; |
| type R = struct { t int32; }; |
| closed protocol T { |
| strict Test() -> (); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way", |
| "response": "l/R" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member error add", |
| before: ` |
| library l; |
| closed protocol T { |
| strict Test() -> (); |
| }; |
| `, |
| after: ` |
| library l; |
| closed protocol T { |
| strict Test() -> () error uint32; |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way", |
| "response": "l/T_Test_Response", |
| "error": "uint32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member error remove", |
| before: ` |
| library l; |
| closed protocol T { |
| strict Test() -> () error uint32; |
| }; |
| `, |
| after: ` |
| library l; |
| closed protocol T { |
| strict Test() -> (); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/T.Test", |
| "before": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way", |
| "response": "l/T_Test_Response", |
| "error": "uint32" |
| }, |
| "after": { |
| "kind": "protocol/member", |
| "name": "l/T.Test", |
| "strictness": "strict", |
| "ordinal": "7985249320572540149", |
| "direction": "two_way" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| { |
| name: "protocol member type change", |
| before: ` |
| library l; |
| closed protocol T { |
| strict Test(struct { t int32; }) -> (); |
| }; |
| `, |
| after: ` |
| library l; |
| closed protocol T { |
| strict Test(struct { t int32; u int32; }) -> (); |
| }; |
| `, |
| expected: ` |
| { |
| "api_diff": [ |
| { |
| "name": "l/TTestRequest.u", |
| "after": { |
| "kind": "struct/member", |
| "name": "l/TTestRequest.u", |
| "ordinal": "2", |
| "type": "int32" |
| }, |
| "conclusion": "APIBreaking" |
| } |
| ] |
| } |
| `, |
| }, |
| // TODO(https://fxbug.dev/42158155): Add aliases and newtypes to summaries and diffs |
| // once they are fully implemented. |
| } |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| c := fidlgentest.EndToEndTest{T: t} |
| brd := strings.NewReader( |
| summarizeOne(t, c.Single(test.before))) |
| ard := strings.NewReader( |
| summarizeOne(t, c.Single(test.after))) |
| summaries, err := summarize.LoadSummariesJSON(brd, ard) |
| if err != nil { |
| t.Fatalf("while loading summaries: %v", err) |
| } |
| actual, err := Compute(summaries[0], summaries[1]) |
| if err != nil { |
| t.Fatalf("while computing diff: %v", err) |
| } |
| var expected Report |
| if err := json.Unmarshal([]byte(test.expected), &expected); err != nil { |
| t.Fatalf("unexpected error while unmarshaling expected data: %v", err) |
| } |
| if diff := cmp.Diff(expected, actual, cmpOptions); diff != "" { |
| t.Errorf("want:\n\t%+v\n\tgot:\n\t%+v\n\tdiff:\n\t%v", expected, actual, diff) |
| } |
| }) |
| } |
| } |
| |
| func summarizeOne(t *testing.T, r fidlgen.Root) string { |
| t.Helper() |
| s := summarize.Summarize(r) |
| var buf strings.Builder |
| if err := s.WriteJSON(&buf); err != nil { |
| t.Fatalf("error while summarizing: %v", err) |
| } |
| return buf.String() |
| } |