package fidlgen

import (
	"github.com/google/go-cmp/cmp"
	"testing"
)

func TestBuildPaddingMarkersWithoutFlattening(t *testing.T) {
	type testCase struct {
		name string
		in   Struct
		out  []PaddingMarker
	}
	testCases := []testCase{
		{
			name: "empty",
			in:   Struct{},
			out:  nil,
		},
		{
			name: "no padding 8-bytes",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 8,
					Alignment:  8,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  4,
							Padding: 0,
						},
					},
				},
			},
			out: nil,
		},
		{
			name: "no padding 4-bytes",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 4,
					Alignment:  4,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  2,
							Padding: 0,
						},
					},
				},
			},
			out: nil,
		},
		{
			name: "no padding 2-bytes",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 2,
					Alignment:  2,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  1,
							Padding: 0,
						},
					},
				},
			},
			out: nil,
		},
		{
			name: "no padding 1-byte",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 1,
					Alignment:  1,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
				},
			},
			out: nil,
		},
		{
			name: "8-byte struct with 2 bytes of padding at end",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 8,
					Alignment:  8,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  4,
							Padding: 2,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff},
				},
			},
		},
		{
			name: "4-byte struct with 1 byte of padding at end",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 4,
					Alignment:  4,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  2,
							Padding: 1,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0x00, 0x00, 0xff},
				},
			},
		},
		{
			name: "2-byte struct with 1 byte padding at end",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 2,
					Alignment:  2,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 1,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0xff},
				},
			},
		},
		{
			name: "padding at end of 8-byte chunk, before next chunk",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 16,
					Alignment:  8,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  4,
							Padding: 2,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  8,
							Padding: 0,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff},
				},
			},
		},
		{
			name: "padding in middle of 4-byte block",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 4,
					Alignment:  4,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 1,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  2,
							Padding: 0,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0xff, 0x00, 0x00},
				},
			},
		},
		{
			name: "8 byte mask with non-zero offset",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 16,
					Alignment:  8,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  8,
							Padding: 4,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 8,
					Mask:   []byte{0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff},
				},
			},
		},
		{
			name: "4 byte mask with non-zero offset",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 12,
					Alignment:  4,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  8,
							Padding: 2,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 8,
					Mask:   []byte{0x00, 0x00, 0xff, 0xff},
				},
			},
		},
		{
			name: "2 byte mask with non-zero offset",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 10,
					Alignment:  2,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  8,
							Padding: 1,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 8,
					Mask:   []byte{0x00, 0xff},
				},
			},
		},
	}
	for _, testCase := range testCases {
		out := testCase.in.BuildPaddingMarkers(WireFormatVersionV1)
		if diff := cmp.Diff(testCase.out, out); diff != "" {
			t.Errorf("%s:\nexpected != actual (-want +got)\n%s", testCase.name, diff)
		}
	}
}

func TestBuildPaddingMarkersFlatteningStruct(t *testing.T) {
	var innerStructIdentifier EncodedCompoundIdentifier = "abcd"
	innerStruct := Struct{
		TypeShapeV1: TypeShape{
			InlineSize: 4,
			Alignment:  4,
		},
		Members: []StructMember{
			{
				FieldShapeV1: FieldShape{
					Offset:  0,
					Padding: 3,
				},
			},
		},
	}
	input := Struct{
		TypeShapeV1: TypeShape{
			InlineSize: 8,
			Alignment:  4,
		},
		Members: []StructMember{
			{
				FieldShapeV1: FieldShape{
					Offset:  0,
					Padding: 4,
				},
				Type: Type{
					Kind:       IdentifierType,
					Identifier: innerStructIdentifier,
				},
			},
		},
	}
	out := input.BuildFlattenedPaddingMarkers(WireFormatVersionV1, func(identifier EncodedCompoundIdentifier) *Struct {
		if identifier == innerStructIdentifier {
			return &innerStruct
		}
		return nil
	})
	expected := []PaddingMarker{
		{
			Offset: 0,
			Mask:   []byte{0x00, 0xff, 0xff, 0xff},
		},
		{
			Offset: 4,
			Mask:   []byte{0xff, 0xff, 0xff, 0xff},
		},
	}
	if diff := cmp.Diff(expected, out); diff != "" {
		t.Errorf("expected != actual (-want +got)\n%s", diff)
	}
}

func TestBuildPaddingMarkersFlatteningArray(t *testing.T) {
	var innerStructIdentifier EncodedCompoundIdentifier = "abcd"
	innerStruct := Struct{
		TypeShapeV1: TypeShape{
			InlineSize: 4,
			Alignment:  4,
		},
		Members: []StructMember{
			{
				FieldShapeV1: FieldShape{
					Offset:  0,
					Padding: 3,
				},
			},
		},
	}
	count := 3
	input := Struct{
		TypeShapeV1: TypeShape{
			InlineSize: 12,
			Alignment:  4,
		},
		Members: []StructMember{
			{
				FieldShapeV1: FieldShape{
					Offset:  0,
					Padding: 0,
				},
				Type: Type{
					Kind:         ArrayType,
					ElementCount: &count,
					ElementType: &Type{
						Kind:       IdentifierType,
						Identifier: innerStructIdentifier,
					},
				},
			},
		},
	}
	out := input.BuildFlattenedPaddingMarkers(WireFormatVersionV1, func(identifier EncodedCompoundIdentifier) *Struct {
		if identifier == innerStructIdentifier {
			return &innerStruct
		}
		return nil
	})
	expected := []PaddingMarker{
		{
			Offset: 0,
			Mask:   []uint8{0x00, 0x0ff, 0xff, 0xff},
		},
		{
			Offset: 4,
			Mask:   []uint8{0x00, 0xff, 0xff, 0xff},
		},
		{
			Offset: 8,
			Mask:   []byte{0x00, 0xff, 0xff, 0xff},
		},
	}
	if diff := cmp.Diff(expected, out); diff != "" {
		t.Errorf("expected != actual (-want +got)\n%s", diff)
	}
}
// Copyright 2022 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 fidlgen

import (
	"github.com/google/go-cmp/cmp"
	"testing"
)

func TestBuildPaddingMarkersWithoutFlattening(t *testing.T) {
	type testCase struct {
		name string
		in   Struct
		out  []PaddingMarker
	}
	testCases := []testCase{
		{
			name: "empty",
			in:   Struct{},
			out:  nil,
		},
		{
			name: "no padding 8-bytes",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 8,
					Alignment:  8,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  4,
							Padding: 0,
						},
					},
				},
			},
			out: nil,
		},
		{
			name: "no padding 4-bytes",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 4,
					Alignment:  4,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  2,
							Padding: 0,
						},
					},
				},
			},
			out: nil,
		},
		{
			name: "no padding 2-bytes",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 2,
					Alignment:  2,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  1,
							Padding: 0,
						},
					},
				},
			},
			out: nil,
		},
		{
			name: "no padding 1-byte",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 1,
					Alignment:  1,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
				},
			},
			out: nil,
		},
		{
			name: "8-byte struct with 2 bytes of padding at end",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 8,
					Alignment:  8,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  4,
							Padding: 2,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff},
				},
			},
		},
		{
			name: "4-byte struct with 1 byte of padding at end",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 4,
					Alignment:  4,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  2,
							Padding: 1,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0x00, 0x00, 0xff},
				},
			},
		},
		{
			name: "2-byte struct with 1 byte padding at end",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 2,
					Alignment:  2,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 1,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0xff},
				},
			},
		},
		{
			name: "padding at end of 8-byte chunk, before next chunk",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 16,
					Alignment:  8,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  4,
							Padding: 2,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  8,
							Padding: 0,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff},
				},
			},
		},
		{
			name: "padding in middle of 4-byte block",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 4,
					Alignment:  4,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 1,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  2,
							Padding: 0,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 0,
					Mask:   []byte{0x00, 0xff, 0x00, 0x00},
				},
			},
		},
		{
			name: "8 byte mask with non-zero offset",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 16,
					Alignment:  8,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  8,
							Padding: 4,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 8,
					Mask:   []byte{0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff},
				},
			},
		},
		{
			name: "4 byte mask with non-zero offset",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 12,
					Alignment:  4,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  8,
							Padding: 2,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 8,
					Mask:   []byte{0x00, 0x00, 0xff, 0xff},
				},
			},
		},
		{
			name: "2 byte mask with non-zero offset",
			in: Struct{
				TypeShapeV1: TypeShape{
					InlineSize: 10,
					Alignment:  2,
				},
				Members: []StructMember{
					{
						FieldShapeV1: FieldShape{
							Offset:  0,
							Padding: 0,
						},
					},
					{
						FieldShapeV1: FieldShape{
							Offset:  8,
							Padding: 1,
						},
					},
				},
			},
			out: []PaddingMarker{
				{
					Offset: 8,
					Mask:   []byte{0x00, 0xff},
				},
			},
		},
	}
	for _, testCase := range testCases {
		out := testCase.in.BuildPaddingMarkers(WireFormatVersionV1)
		if diff := cmp.Diff(testCase.out, out); diff != "" {
			t.Errorf("%s:\nexpected != actual (-want +got)\n%s", testCase.name, diff)
		}
	}
}

func TestBuildPaddingMarkersFlatteningStruct(t *testing.T) {
	var innerStructIdentifier EncodedCompoundIdentifier = "abcd"
	innerStruct := Struct{
		TypeShapeV1: TypeShape{
			InlineSize: 4,
			Alignment:  4,
		},
		Members: []StructMember{
			{
				FieldShapeV1: FieldShape{
					Offset:  0,
					Padding: 3,
				},
			},
		},
	}
	input := Struct{
		TypeShapeV1: TypeShape{
			InlineSize: 8,
			Alignment:  4,
		},
		Members: []StructMember{
			{
				FieldShapeV1: FieldShape{
					Offset:  0,
					Padding: 4,
				},
				Type: Type{
					Kind:       IdentifierType,
					Identifier: innerStructIdentifier,
				},
			},
		},
	}
	out := input.BuildFlattenedPaddingMarkers(WireFormatVersionV1, func(identifier EncodedCompoundIdentifier) *Struct {
		if identifier == innerStructIdentifier {
			return &innerStruct
		}
		return nil
	})
	expected := []PaddingMarker{
		{
			Offset: 0,
			Mask:   []byte{0x00, 0xff, 0xff, 0xff},
		},
		{
			Offset: 4,
			Mask:   []byte{0xff, 0xff, 0xff, 0xff},
		},
	}
	if diff := cmp.Diff(expected, out); diff != "" {
		t.Errorf("expected != actual (-want +got)\n%s", diff)
	}
}

func TestBuildPaddingMarkersFlatteningArray(t *testing.T) {
	var innerStructIdentifier EncodedCompoundIdentifier = "abcd"
	innerStruct := Struct{
		TypeShapeV1: TypeShape{
			InlineSize: 4,
			Alignment:  4,
		},
		Members: []StructMember{
			{
				FieldShapeV1: FieldShape{
					Offset:  0,
					Padding: 3,
				},
			},
		},
	}
	count := 3
	input := Struct{
		TypeShapeV1: TypeShape{
			InlineSize: 12,
			Alignment:  4,
		},
		Members: []StructMember{
			{
				FieldShapeV1: FieldShape{
					Offset:  0,
					Padding: 0,
				},
				Type: Type{
					Kind:         ArrayType,
					ElementCount: &count,
					ElementType: &Type{
						Kind:       IdentifierType,
						Identifier: innerStructIdentifier,
					},
				},
			},
		},
	}
	out := input.BuildFlattenedPaddingMarkers(WireFormatVersionV1, func(identifier EncodedCompoundIdentifier) *Struct {
		if identifier == innerStructIdentifier {
			return &innerStruct
		}
		return nil
	})
	expected := []PaddingMarker{
		{
			Offset: 0,
			Mask:   []uint8{0x00, 0x0ff, 0xff, 0xff},
		},
		{
			Offset: 4,
			Mask:   []uint8{0x00, 0xff, 0xff, 0xff},
		},
		{
			Offset: 8,
			Mask:   []byte{0x00, 0xff, 0xff, 0xff},
		},
	}
	if diff := cmp.Diff(expected, out); diff != "" {
		t.Errorf("expected != actual (-want +got)\n%s", diff)
	}
}
