<commit_before>package aws

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/glue"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
	tfglue "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/glue"
	"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource"
)

func TestAccAWSGluePartition_basic(t *testing.T) {
	rName := acctest.RandomWithPrefix("tf-acc-test")
	parValue := acctest.RandString(10)
	resourceName := "aws_glue_partition.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckGluePartitionDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccGluePartitionBasicConfig(rName, parValue),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					testAccCheckResourceAttrAccountID(resourceName, "catalog_id"),
					resource.TestCheckResourceAttr(resourceName, "database_name", rName),
					resource.TestCheckResourceAttr(resourceName, "partition_values.#", "1"),
					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "partition_values.*", parValue),
					resource.TestCheckResourceAttr(resourceName, "parameters.%", "0"),
					resource.TestCheckResourceAttrSet(resourceName, "creation_time"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSGluePartition_multipleValues(t *testing.T) {
	rName := acctest.RandomWithPrefix("tf-acc-test")
	parValue := acctest.RandString(10)
	parValue2 := acctest.RandString(11)
	resourceName := "aws_glue_partition.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckGluePartitionDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccGluePartitionMultiplePartValueConfig(rName, parValue, parValue2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					resource.TestCheckResourceAttr(resourceName, "partition_values.#", "2"),
					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "partition_values.*", parValue),
					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "partition_values.*", parValue2),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSGluePartition_parameters(t *testing.T) {
	rName := acctest.RandomWithPrefix("tf-acc-test")
	parValue := acctest.RandString(10)
	resourceName := "aws_glue_partition.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckGluePartitionDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccGluePartitionParametersConfig1(rName, parValue, "key1", "value1"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					resource.TestCheckResourceAttr(resourceName, "parameters.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "parameters.key1", "value1"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccGluePartitionParametersConfig2(rName, parValue, "key1", "valueUpdated1", "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					resource.TestCheckResourceAttr(resourceName, "parameters.%", "2"),
					resource.TestCheckResourceAttr(resourceName, "parameters.key1", "valueUpdated1"),
					resource.TestCheckResourceAttr(resourceName, "parameters.key2", "value2"),
				),
			},
			{
				Config: testAccGluePartitionParametersConfig1(rName, parValue, "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					resource.TestCheckResourceAttr(resourceName, "parameters.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "parameters.key2", "value2"),
				),
			},
		},
	})
}

func TestAccAWSGluePartition_disappears(t *testing.T) {
	rName := acctest.RandomWithPrefix("tf-acc-test")
	parValue := acctest.RandString(10)
	resourceName := "aws_glue_partition.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckGluePartitionDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccGluePartitionBasicConfig(rName, parValue),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					testAccCheckResourceDisappears(testAccProvider, resourceAwsGluePartition(), resourceName),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func testAccCheckGluePartitionDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).glueconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_glue_partition" {
			continue
		}

		catalogID, dbName, tableName, values, err := tfglue.ReadAwsGluePartitionID(rs.Primary.ID)
		if err != nil {
			return err
		}

		input := &glue.GetPartitionInput{
			DatabaseName:    aws.String(dbName),
			CatalogId:       aws.String(catalogID),
			TableName:       aws.String(tableName),
			PartitionValues: aws.StringSlice(values),
		}
		if _, err := conn.GetPartition(input); err != nil {
			if isAWSErr(err, glue.ErrCodeEntityNotFoundException, "") {
				continue
			}

			return err
		}
		return fmt.Errorf("still exists")
	}
	return nil
}

func testAccCheckGluePartitionExists(name string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[name]
		if !ok {
			return fmt.Errorf("Not found: %s", name)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		catalogID, dbName, tableName, values, err := tfglue.ReadAwsGluePartitionID(rs.Primary.ID)
		if err != nil {
			return err
		}

		conn := testAccProvider.Meta().(*AWSClient).glueconn
		out, err := conn.GetPartition(&glue.GetPartitionInput{
			DatabaseName:    aws.String(dbName),
			CatalogId:       aws.String(catalogID),
			TableName:       aws.String(tableName),
			PartitionValues: aws.StringSlice(values),
		})

		if err != nil {
			return err
		}

		if out.Partition == nil {
			return fmt.Errorf("No Glue Partition Found")
		}

		if !reflect.DeepEqual(aws.StringValueSlice(out.Partition.Values), values) {
			return fmt.Errorf("Glue Partition Mismatch")
		}

		return nil
	}
}

func testAccGluePartitionConfigBase(rName string) string {
	return fmt.Sprintf(`
resource "aws_glue_catalog_database" "test" {
  name = %[1]q
}

resource "aws_glue_catalog_table" "test" {
  name          = %[1]q
  database_name = aws_glue_catalog_database.test.name

  storage_descriptor {
    bucket_columns            = ["bucket_column_1"]
    compressed                = false
    input_format              = "SequenceFileInputFormat"
    location                  = "my_location"
    number_of_buckets         = 1
    output_format             = "SequenceFileInputFormat"
    stored_as_sub_directories = false

    parameters = {
      param1 = "param1_val"
    }

    columns {
      name    = "my_column_1"
      type    = "int"
      comment = "my_column1_comment"
    }

    columns {
      name    = "my_column_2"
      type    = "string"
      comment = "my_column2_comment"
    }

    ser_de_info {
      name = "ser_de_name"

      parameters = {
        param1 = "param_val_1"
      }

      serialization_library = "org.apache.hadoop.hive.serde2.columnar.ColumnarSerDe"
    }

    sort_columns {
      column     = "my_column_1"
      sort_order = 1
    }

    skewed_info {
      skewed_column_names = [
        "my_column_1",
      ]

      skewed_column_value_location_maps = {
        my_column_1 = "my_column_1_val_loc_map"
      }

      skewed_column_values = [
        "skewed_val_1",
      ]
    }
  }

  partition_keys {
    name    = "my_column_12"
    type    = "date"
    comment = "my_column_1_comment2"
  }
}
`, rName)
}

func testAccGluePartitionBasicConfig(rName, parValue string) string {
	return testAccGluePartitionConfigBase(rName) +
		fmt.Sprintf(`
resource "aws_glue_partition" "test" {
  database_name    = aws_glue_catalog_database.test.name
  table_name       = aws_glue_catalog_table.test.name
  partition_values = ["%[1]s"]
}
`, parValue)
}

func testAccGluePartitionParametersConfig1(rName, parValue, key1, value1 string) string {
	return testAccGluePartitionConfigBase(rName) +
		fmt.Sprintf(`
resource "aws_glue_partition" "test" {
  database_name    = aws_glue_catalog_database.test.name
  table_name       = aws_glue_catalog_table.test.name
  partition_values = ["%[1]s"]

  parameters = {
    %[2]q = %[3]q
  }
}
`, parValue, key1, value1)
}

func testAccGluePartitionParametersConfig2(rName, parValue, key1, value1, key2, value2 string) string {
	return testAccGluePartitionConfigBase(rName) +
		fmt.Sprintf(`
resource "aws_glue_partition" "test" {
  database_name    = aws_glue_catalog_database.test.name
  table_name       = aws_glue_catalog_table.test.name
  partition_values = ["%[1]s"]

  parameters = {
    %[2]q = %[3]q
    %[4]q = %[5]q
  }
}
`, parValue, key1, value1, key2, value2)
}

func testAccGluePartitionMultiplePartValueConfig(rName, parValue, parValue2 string) string {
	return fmt.Sprintf(`
resource "aws_glue_catalog_table" "test" {
  name          = %[1]q
  database_name = aws_glue_catalog_database.test.name

  storage_descriptor {
    bucket_columns            = ["bucket_column_1"]
    compressed                = false
    input_format              = "SequenceFileInputFormat"
    location                  = "my_location"
    number_of_buckets         = 1
    output_format             = "SequenceFileInputFormat"
    stored_as_sub_directories = false

    parameters = {
      param1 = "param1_val"
    }

    columns {
      name    = "my_column_1"
      type    = "int"
      comment = "my_column1_comment"
    }

    columns {
      name    = "my_column_2"
      type    = "string"
      comment = "my_column2_comment"
    }

    ser_de_info {
      name = "ser_de_name"

      parameters = {
        param1 = "param_val_1"
      }

      serialization_library = "org.apache.hadoop.hive.serde2.columnar.ColumnarSerDe"
    }

    sort_columns {
      column     = "my_column_1"
      sort_order = 1
    }

    skewed_info {
      skewed_column_names = [
        "my_column_1",
      ]

      skewed_column_value_location_maps = {
        my_column_1 = "my_column_1_val_loc_map"
      }

      skewed_column_values = [
        "skewed_val_1",
      ]
    }
  }

  partition_keys {
    name    = "my_column_12"
    type    = "date"
    comment = "my_column_1_comment2"
  }

  partition_keys {
    name    = "my_column_11"
    type    = "date"
    comment = "my_column_1_comment2"
  }
}

resource "aws_glue_partition" "test" {
  database_name    = aws_glue_catalog_database.test.name
  table_name       = aws_glue_catalog_table.test.name
  partition_values = ["%[2]s", "%[3]s"]
}
`, rName, parValue, parValue2)
}
<commit_msg>test lint<commit_after>package aws

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/glue"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
	tfglue "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/glue"
	"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource"
)

func TestAccAWSGluePartition_basic(t *testing.T) {
	rName := acctest.RandomWithPrefix("tf-acc-test")
	parValue := acctest.RandString(10)
	resourceName := "aws_glue_partition.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckGluePartitionDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccGluePartitionBasicConfig(rName, parValue),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					testAccCheckResourceAttrAccountID(resourceName, "catalog_id"),
					resource.TestCheckResourceAttr(resourceName, "database_name", rName),
					resource.TestCheckResourceAttr(resourceName, "partition_values.#", "1"),
					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "partition_values.*", parValue),
					resource.TestCheckResourceAttr(resourceName, "parameters.%", "0"),
					resource.TestCheckResourceAttrSet(resourceName, "creation_time"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSGluePartition_multipleValues(t *testing.T) {
	rName := acctest.RandomWithPrefix("tf-acc-test")
	parValue := acctest.RandString(10)
	parValue2 := acctest.RandString(11)
	resourceName := "aws_glue_partition.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckGluePartitionDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccGluePartitionMultiplePartValueConfig(rName, parValue, parValue2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					resource.TestCheckResourceAttr(resourceName, "partition_values.#", "2"),
					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "partition_values.*", parValue),
					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "partition_values.*", parValue2),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSGluePartition_parameters(t *testing.T) {
	rName := acctest.RandomWithPrefix("tf-acc-test")
	parValue := acctest.RandString(10)
	resourceName := "aws_glue_partition.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckGluePartitionDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccGluePartitionParametersConfig1(rName, parValue, "key1", "value1"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					resource.TestCheckResourceAttr(resourceName, "parameters.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "parameters.key1", "value1"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccGluePartitionParametersConfig2(rName, parValue, "key1", "valueUpdated1", "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					resource.TestCheckResourceAttr(resourceName, "parameters.%", "2"),
					resource.TestCheckResourceAttr(resourceName, "parameters.key1", "valueUpdated1"),
					resource.TestCheckResourceAttr(resourceName, "parameters.key2", "value2"),
				),
			},
			{
				Config: testAccGluePartitionParametersConfig1(rName, parValue, "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					resource.TestCheckResourceAttr(resourceName, "parameters.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "parameters.key2", "value2"),
				),
			},
		},
	})
}

func TestAccAWSGluePartition_disappears(t *testing.T) {
	rName := acctest.RandomWithPrefix("tf-acc-test")
	parValue := acctest.RandString(10)
	resourceName := "aws_glue_partition.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckGluePartitionDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccGluePartitionBasicConfig(rName, parValue),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckGluePartitionExists(resourceName),
					testAccCheckResourceDisappears(testAccProvider, resourceAwsGluePartition(), resourceName),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func testAccCheckGluePartitionDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).glueconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_glue_partition" {
			continue
		}

		catalogID, dbName, tableName, values, err := tfglue.ReadAwsGluePartitionID(rs.Primary.ID)
		if err != nil {
			return err
		}

		input := &glue.GetPartitionInput{
			DatabaseName:    aws.String(dbName),
			CatalogId:       aws.String(catalogID),
			TableName:       aws.String(tableName),
			PartitionValues: aws.StringSlice(values),
		}
		if _, err := conn.GetPartition(input); err != nil {
			if isAWSErr(err, glue.ErrCodeEntityNotFoundException, "") {
				continue
			}

			return err
		}
		return fmt.Errorf("still exists")
	}
	return nil
}

func testAccCheckGluePartitionExists(name string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[name]
		if !ok {
			return fmt.Errorf("Not found: %s", name)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		catalogID, dbName, tableName, values, err := tfglue.ReadAwsGluePartitionID(rs.Primary.ID)
		if err != nil {
			return err
		}

		conn := testAccProvider.Meta().(*AWSClient).glueconn
		out, err := conn.GetPartition(&glue.GetPartitionInput{
			DatabaseName:    aws.String(dbName),
			CatalogId:       aws.String(catalogID),
			TableName:       aws.String(tableName),
			PartitionValues: aws.StringSlice(values),
		})

		if err != nil {
			return err
		}

		if out.Partition == nil {
			return fmt.Errorf("No Glue Partition Found")
		}

		if !reflect.DeepEqual(aws.StringValueSlice(out.Partition.Values), values) {
			return fmt.Errorf("Glue Partition Mismatch")
		}

		return nil
	}
}

func testAccGluePartitionConfigBase(rName string) string {
	return fmt.Sprintf(`
resource "aws_glue_catalog_database" "test" {
  name = %[1]q
}

resource "aws_glue_catalog_table" "test" {
  name          = %[1]q
  database_name = aws_glue_catalog_database.test.name

  storage_descriptor {
    bucket_columns            = ["bucket_column_1"]
    compressed                = false
    input_format              = "SequenceFileInputFormat"
    location                  = "my_location"
    number_of_buckets         = 1
    output_format             = "SequenceFileInputFormat"
    stored_as_sub_directories = false

    parameters = {
      param1 = "param1_val"
    }

    columns {
      name    = "my_column_1"
      type    = "int"
      comment = "my_column1_comment"
    }

    columns {
      name    = "my_column_2"
      type    = "string"
      comment = "my_column2_comment"
    }

    ser_de_info {
      name = "ser_de_name"

      parameters = {
        param1 = "param_val_1"
      }

      serialization_library = "org.apache.hadoop.hive.serde2.columnar.ColumnarSerDe"
    }

    sort_columns {
      column     = "my_column_1"
      sort_order = 1
    }

    skewed_info {
      skewed_column_names = [
        "my_column_1",
      ]

      skewed_column_value_location_maps = {
        my_column_1 = "my_column_1_val_loc_map"
      }

      skewed_column_values = [
        "skewed_val_1",
      ]
    }
  }

  partition_keys {
    name    = "my_column_12"
    type    = "date"
    comment = "my_column_1_comment2"
  }
}
`, rName)
}

func testAccGluePartitionBasicConfig(rName, parValue string) string {
	return testAccGluePartitionConfigBase(rName) +
		fmt.Sprintf(`
resource "aws_glue_partition" "test" {
  database_name    = aws_glue_catalog_database.test.name
  table_name       = aws_glue_catalog_table.test.name
  partition_values = ["%[1]s"]
}
`, parValue)
}

func testAccGluePartitionParametersConfig1(rName, parValue, key1, value1 string) string {
	return testAccGluePartitionConfigBase(rName) +
		fmt.Sprintf(`
resource "aws_glue_partition" "test" {
  database_name    = aws_glue_catalog_database.test.name
  table_name       = aws_glue_catalog_table.test.name
  partition_values = ["%[1]s"]

  parameters = {
    %[2]q = %[3]q
  }
}
`, parValue, key1, value1)
}

func testAccGluePartitionParametersConfig2(rName, parValue, key1, value1, key2, value2 string) string {
	return testAccGluePartitionConfigBase(rName) +
		fmt.Sprintf(`
resource "aws_glue_partition" "test" {
  database_name    = aws_glue_catalog_database.test.name
  table_name       = aws_glue_catalog_table.test.name
  partition_values = ["%[1]s"]

  parameters = {
    %[2]q = %[3]q
    %[4]q = %[5]q
  }
}
`, parValue, key1, value1, key2, value2)
}

func testAccGluePartitionMultiplePartValueConfig(rName, parValue, parValue2 string) string {
	return fmt.Sprintf(`
resource "aws_glue_catalog_table" "test" {
  name          = %[1]q
  database_name = aws_glue_catalog_database.test.name

  storage_descriptor {
    bucket_columns            = ["bucket_column_1"]
    compressed                = false
    input_format              = "SequenceFileInputFormat"
    location                  = "my_location"
    number_of_buckets         = 1
    output_format             = "SequenceFileInputFormat"
    stored_as_sub_directories = false

    parameters = {
      param1 = "param1_val"
    }

    columns {
      name    = "my_column_1"
      type    = "int"
      comment = "my_column1_comment"
    }

    columns {
      name    = "my_column_2"
      type    = "string"
      comment = "my_column2_comment"
    }

    ser_de_info {
      name = "ser_de_name"

      parameters = {
        param1 = "param_val_1"
      }

      serialization_library = "org.apache.hadoop.hive.serde2.columnar.ColumnarSerDe"
    }

    sort_columns {
      column     = "my_column_1"
      sort_order = 1
    }

    skewed_info {
      skewed_column_names = [
        "my_column_1",
      ]

      skewed_column_value_location_maps = {
        my_column_1 = "my_column_1_val_loc_map"
      }

      skewed_column_values = [
        "skewed_val_1",
      ]
    }
  }

  partition_keys {
    name    = "my_column_12"
    type    = "date"
    comment = "my_column_1_comment2"
  }

  partition_keys {
    name    = "my_column_11"
    type    = "date"
    comment = "my_column_1_comment2"
  }
}

resource "aws_glue_partition" "test" {
  database_name    = aws_glue_catalog_database.test.name
  table_name       = aws_glue_catalog_table.test.name
  partition_values = ["%[2]s", "%[3]s"]
}
`, rName, parValue, parValue2)
}
<|endoftext|>


<commit_before>package cell_test

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"time"

	archive_helper "code.cloudfoundry.org/archiver/extractor/test_helper"
	"code.cloudfoundry.org/bbs"
	"code.cloudfoundry.org/bbs/models"
	"code.cloudfoundry.org/durationjson"
	"code.cloudfoundry.org/inigo/fixtures"
	"code.cloudfoundry.org/inigo/helpers"
	"code.cloudfoundry.org/lager"
	repconfig "code.cloudfoundry.org/rep/cmd/rep/config"
	routeemitterconfig "code.cloudfoundry.org/route-emitter/cmd/route-emitter/config"
	"code.cloudfoundry.org/routing-info/cfroutes"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
	"github.com/tedsuo/ifrit/grouper"
)

var _ = Describe("LocalRouteEmitter", func() {
	var (
		processGuid                                  string
		runtime                                      ifrit.Process
		archiveFiles                                 []archive_helper.ArchiveFile
		fileServerStaticDir                          string
		cellAID, cellBID, cellARepAddr, cellBRepAddr string
	)

	BeforeEach(func() {
		processGuid = helpers.GenerateGuid()

		var fileServer ifrit.Runner
		fileServer, fileServerStaticDir = componentMaker.FileServer()

		cellAID = "cell-a"
		cellBID = "cell-b"

		cellARepAddr = fmt.Sprintf("0.0.0.0:%d", 14200+GinkgoParallelNode())
		repA := componentMaker.RepN(1, func(config *repconfig.RepConfig) {
			config.CellID = cellAID
			config.ListenAddr = cellARepAddr
			config.EvacuationTimeout = durationjson.Duration(30 * time.Second)
		})

		cellBRepAddr = fmt.Sprintf("0.0.0.0:%d", 14400+GinkgoParallelNode())
		repB := componentMaker.RepN(2, func(config *repconfig.RepConfig) {
			config.CellID = cellBID
			config.ListenAddr = cellBRepAddr
			config.EvacuationTimeout = durationjson.Duration(30 * time.Second)
		})

		runtime = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"router", componentMaker.Router()},
			{"file-server", fileServer},
			{"rep-1", repA},
			{"rep-2", repB},
			{"auctioneer", componentMaker.Auctioneer()},
			// override syncinterval and cell id
			{"route-emitter-1", componentMaker.RouteEmitterN(1, func(config *routeemitterconfig.RouteEmitterConfig) {
				config.SyncInterval = durationjson.Duration(time.Hour)
				config.CellID = cellAID
			})},
			{"route-emitter-2", componentMaker.RouteEmitterN(2, func(config *routeemitterconfig.RouteEmitterConfig) {
				config.SyncInterval = durationjson.Duration(time.Hour)
				config.CellID = cellBID
			})},
		}))
		archiveFiles = fixtures.GoServerApp()
	})

	AfterEach(func() {
		helpers.StopProcesses(runtime)
	})

	JustBeforeEach(func() {
		archive_helper.CreateZipArchive(
			filepath.Join(fileServerStaticDir, "lrp.zip"),
			archiveFiles,
		)
	})

	Describe("desiring", func() {
		var (
			lrp       *models.DesiredLRP
			instances int32
		)

		BeforeEach(func() {
			instances = 1
		})

		JustBeforeEach(func() {
			lrp = createDesiredLRP(processGuid)
			lrp.Instances = instances
			err := bbsClient.DesireLRP(logger, lrp)
			Expect(err).NotTo(HaveOccurred())
			Eventually(helpers.LRPStatePoller(logger, bbsClient, processGuid, nil)).Should(Equal(models.ActualLRPStateRunning))
		})

		It("eventually is accessible through the router within a second", func() {
			Eventually(
				helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, helpers.DefaultHost),
				time.Second,
				10*time.Millisecond,
			).Should(Equal(http.StatusOK))
		})

		Context("when there are 3 instances", func() {
			BeforeEach(func() {
				instances = 3
			})

			Context("and a rep start evacuating", func() {
				JustBeforeEach(func() {
					evacuateARep(
						processGuid,
						logger,
						bbsClient,
						cellAID, cellARepAddr,
						cellBID, cellBRepAddr,
					)
				})

				It("eventually should make the new lrp routable within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(ConsistOf([]string{"0", "1", "2"}))
				})
			})

			Context("and the app is deleted", func() {
				JustBeforeEach(func() {
					err := bbsClient.RemoveDesiredLRP(logger, processGuid)
					Expect(err).NotTo(HaveOccurred())
				})

				It("eventually is not accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(BeEmpty())
				})
			})

			Context("and the app is updated", func() {
				var (
					desiredLRPUdate *models.DesiredLRPUpdate
				)

				BeforeEach(func() {
					desiredLRPUdate = &models.DesiredLRPUpdate{}
				})

				JustBeforeEach(func() {
					err := bbsClient.UpdateDesiredLRP(logger, processGuid, desiredLRPUdate)
					Expect(err).NotTo(HaveOccurred())
				})

				Context("to scale the app down", func() {
					BeforeEach(func() {
						newInstances := int32(1)
						desiredLRPUdate.Instances = &newInstances
					})

					It("eventually extra routes are removed within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
							time.Second,
							10*time.Millisecond,
						).Should(ConsistOf([]string{"0"}))
					})
				})

				Context("to add new route", func() {
					BeforeEach(func() {
						routes := cfroutes.CFRoutes{{Hostnames: []string{helpers.DefaultHost, "some-other-route"}, Port: 8080}}.RoutingInfo()
						desiredLRPUdate.Routes = &routes
					})

					It("eventually is accessible using the new route within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, "some-other-route"),
							time.Second,
							10*time.Millisecond,
						).Should(ConsistOf([]string{"0", "1", "2"}))
					})
				})

				Context("and all routes are deleted", func() {
					BeforeEach(func() {
						routes := cfroutes.CFRoutes{}.RoutingInfo()
						desiredLRPUdate.Routes = &routes
					})

					It("eventually not accessible using its route within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
							time.Second,
							10*time.Millisecond,
						).Should(BeEmpty())
					})
				})
			})
		})

		Context("when the instances count change", func() {
			var (
				newInstances int32
			)

			JustBeforeEach(func() {
				err := bbsClient.UpdateDesiredLRP(logger, processGuid, &models.DesiredLRPUpdate{
					Instances: &newInstances,
				})
				Expect(err).NotTo(HaveOccurred())
			})

			Context("to 3 instances", func() {
				BeforeEach(func() {
					newInstances = 3
				})

				JustBeforeEach(func() {
					for i := 1; i < int(newInstances); i++ {
						Eventually(helpers.LRPInstanceStatePoller(logger, bbsClient, processGuid, i, nil)).Should(Equal(models.ActualLRPStateRunning))
					}
				})

				It("eventually is accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(ConsistOf([]string{"0", "1", "2"}))
				})
			})

			Context("to 0 instances", func() {
				BeforeEach(func() {
					newInstances = 0
				})

				It("eventually is not accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(BeEmpty())
				})
			})
		})
	})
})

func createDesiredLRP(processGuid string) *models.DesiredLRP {
	lrp := helpers.DefaultLRPCreateRequest(processGuid, "log-guid", 1)
	lrp.Setup = nil
	lrp.CachedDependencies = []*models.CachedDependency{{
		From:      fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "lrp.zip"),
		To:        "/tmp/diego/lrp",
		Name:      "lrp bits",
		CacheKey:  "lrp-cache-key",
		LogSource: "APP",
	}}
	lrp.LegacyDownloadUser = "vcap"
	lrp.Privileged = true
	lrp.Action = models.WrapAction(&models.RunAction{
		User: "vcap",
		Path: "/tmp/diego/lrp/go-server",
		Env:  []*models.EnvironmentVariable{{"PORT", "8080"}},
	})
	routes := cfroutes.CFRoutes{{Hostnames: []string{helpers.DefaultHost}, Port: 8080}}.RoutingInfo()
	lrp.Routes = &routes
	return lrp
}

func evacuateARep(
	processGuid string,
	logger lager.Logger,
	bbsClient bbs.InternalClient,
	cellAID, cellARepAddr string,
	cellBID, cellBRepAddr string,
) {
	By("finding rep with one instance running")
	actualLRPGroups, err := bbsClient.ActualLRPGroupsByProcessGuid(logger, processGuid)
	Expect(err).NotTo(HaveOccurred())
	Expect(actualLRPGroups).To(HaveLen(3))
	instancePerRepCount := map[string]int{}
	for _, lrpGroup := range actualLRPGroups {
		lrp, _ := lrpGroup.Resolve()
		cellID := lrp.ActualLRPInstanceKey.CellId
		instancePerRepCount[cellID]++
	}
	repWithOneInstance := ""
	for cellID, count := range instancePerRepCount {
		if count == 1 {
			repWithOneInstance = cellID
			break
		}
	}
	Expect(repWithOneInstance).NotTo(BeEmpty())

	evacuatingRepAddr := ""
	otherRepID := ""
	if repWithOneInstance == cellAID {
		evacuatingRepAddr = cellARepAddr
		otherRepID = cellBID
	} else if repWithOneInstance == cellBID {
		evacuatingRepAddr = cellBRepAddr
		otherRepID = cellAID
	} else {
		Fail(fmt.Sprintf("cell id %s doesn't match either cell-a or cell-b", repWithOneInstance))
	}

	By(fmt.Sprintf("sending evacuate request to %s", repWithOneInstance))
	resp, err := http.Post(fmt.Sprintf("http://%s/evacuate", evacuatingRepAddr), "text/html", nil)
	Expect(err).NotTo(HaveOccurred())
	resp.Body.Close()
	Expect(resp.StatusCode).To(Equal(http.StatusAccepted))

	By("waiting for the lrp to run on the new cell")
	Eventually(func() map[string]int {
		lrps := helpers.RunningActualLRPs(logger, bbsClient, processGuid)
		cellIDs := map[string]int{}
		for _, lrp := range lrps {
			cellIDs[lrp.CellId]++
		}
		return cellIDs
	}).Should(Equal(map[string]int{otherRepID: 3}))
}
<commit_msg>start rep and local route emitter in separate ifrit process<commit_after>package cell_test

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"time"

	archive_helper "code.cloudfoundry.org/archiver/extractor/test_helper"
	"code.cloudfoundry.org/bbs"
	"code.cloudfoundry.org/bbs/models"
	"code.cloudfoundry.org/durationjson"
	"code.cloudfoundry.org/inigo/fixtures"
	"code.cloudfoundry.org/inigo/helpers"
	"code.cloudfoundry.org/lager"
	repconfig "code.cloudfoundry.org/rep/cmd/rep/config"
	routeemitterconfig "code.cloudfoundry.org/route-emitter/cmd/route-emitter/config"
	"code.cloudfoundry.org/routing-info/cfroutes"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
	"github.com/tedsuo/ifrit/grouper"
)

var _ = Describe("LocalRouteEmitter", func() {
	var (
		processGuid                                  string
		runtime, cellAProcess, cellBProcess          ifrit.Process
		archiveFiles                                 []archive_helper.ArchiveFile
		fileServerStaticDir                          string
		cellAID, cellBID, cellARepAddr, cellBRepAddr string
	)

	BeforeEach(func() {
		processGuid = helpers.GenerateGuid()

		var fileServer ifrit.Runner
		fileServer, fileServerStaticDir = componentMaker.FileServer()

		cellAID = "cell-a"
		cellBID = "cell-b"

		cellARepAddr = fmt.Sprintf("0.0.0.0:%d", 14200+GinkgoParallelNode())
		repA := componentMaker.RepN(1, func(config *repconfig.RepConfig) {
			config.CellID = cellAID
			config.ListenAddr = cellARepAddr
			config.EvacuationTimeout = durationjson.Duration(30 * time.Second)
		})

		cellBRepAddr = fmt.Sprintf("0.0.0.0:%d", 14400+GinkgoParallelNode())
		repB := componentMaker.RepN(2, func(config *repconfig.RepConfig) {
			config.CellID = cellBID
			config.ListenAddr = cellBRepAddr
			config.EvacuationTimeout = durationjson.Duration(30 * time.Second)
		})

		runtime = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"router", componentMaker.Router()},
			{"file-server", fileServer},
			{"auctioneer", componentMaker.Auctioneer()},
		}))

		cellAProcess = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"rep-a", repA},
			{"route-emitter-a", componentMaker.RouteEmitterN(1, func(config *routeemitterconfig.RouteEmitterConfig) {
				config.SyncInterval = durationjson.Duration(time.Hour)
				config.CellID = cellAID
			})},
		}))
		cellBProcess = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"rep-b", repB},
			{"route-emitter-b", componentMaker.RouteEmitterN(1, func(config *routeemitterconfig.RouteEmitterConfig) {
				config.SyncInterval = durationjson.Duration(time.Hour)
				config.CellID = cellBID
			})},
		}))
		archiveFiles = fixtures.GoServerApp()
	})

	AfterEach(func() {
		helpers.StopProcesses(runtime, cellAProcess, cellBProcess)
	})

	JustBeforeEach(func() {
		archive_helper.CreateZipArchive(
			filepath.Join(fileServerStaticDir, "lrp.zip"),
			archiveFiles,
		)
	})

	Describe("desiring", func() {
		var (
			lrp       *models.DesiredLRP
			instances int32
		)

		BeforeEach(func() {
			instances = 1
		})

		JustBeforeEach(func() {
			lrp = createDesiredLRP(processGuid)
			lrp.Instances = instances
			err := bbsClient.DesireLRP(logger, lrp)
			Expect(err).NotTo(HaveOccurred())
			Eventually(helpers.LRPStatePoller(logger, bbsClient, processGuid, nil)).Should(Equal(models.ActualLRPStateRunning))
		})

		It("eventually is accessible through the router within a second", func() {
			Eventually(
				helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, helpers.DefaultHost),
				time.Second,
				10*time.Millisecond,
			).Should(Equal(http.StatusOK))
		})

		Context("when there are 3 instances", func() {
			BeforeEach(func() {
				instances = 3
			})

			Context("and a rep start evacuating", func() {
				JustBeforeEach(func() {
					evacuateARep(
						processGuid,
						logger,
						bbsClient,
						cellAID, cellARepAddr,
						cellBID, cellBRepAddr,
					)
				})

				It("eventually should make the new lrp routable within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(ConsistOf([]string{"0", "1", "2"}))
				})
			})

			Context("and the app is deleted", func() {
				JustBeforeEach(func() {
					err := bbsClient.RemoveDesiredLRP(logger, processGuid)
					Expect(err).NotTo(HaveOccurred())
				})

				It("eventually is not accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(BeEmpty())
				})
			})

			Context("and the app is updated", func() {
				var (
					desiredLRPUdate *models.DesiredLRPUpdate
				)

				BeforeEach(func() {
					desiredLRPUdate = &models.DesiredLRPUpdate{}
				})

				JustBeforeEach(func() {
					err := bbsClient.UpdateDesiredLRP(logger, processGuid, desiredLRPUdate)
					Expect(err).NotTo(HaveOccurred())
				})

				Context("to scale the app down", func() {
					BeforeEach(func() {
						newInstances := int32(1)
						desiredLRPUdate.Instances = &newInstances
					})

					It("eventually extra routes are removed within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
							time.Second,
							10*time.Millisecond,
						).Should(ConsistOf([]string{"0"}))
					})
				})

				Context("to add new route", func() {
					BeforeEach(func() {
						routes := cfroutes.CFRoutes{{Hostnames: []string{helpers.DefaultHost, "some-other-route"}, Port: 8080}}.RoutingInfo()
						desiredLRPUdate.Routes = &routes
					})

					It("eventually is accessible using the new route within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, "some-other-route"),
							time.Second,
							10*time.Millisecond,
						).Should(ConsistOf([]string{"0", "1", "2"}))
					})
				})

				Context("and all routes are deleted", func() {
					BeforeEach(func() {
						routes := cfroutes.CFRoutes{}.RoutingInfo()
						desiredLRPUdate.Routes = &routes
					})

					It("eventually not accessible using its route within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
							time.Second,
							10*time.Millisecond,
						).Should(BeEmpty())
					})
				})
			})
		})

		Context("when the instances count change", func() {
			var (
				newInstances int32
			)

			JustBeforeEach(func() {
				err := bbsClient.UpdateDesiredLRP(logger, processGuid, &models.DesiredLRPUpdate{
					Instances: &newInstances,
				})
				Expect(err).NotTo(HaveOccurred())
			})

			Context("to 3 instances", func() {
				BeforeEach(func() {
					newInstances = 3
				})

				JustBeforeEach(func() {
					for i := 1; i < int(newInstances); i++ {
						Eventually(helpers.LRPInstanceStatePoller(logger, bbsClient, processGuid, i, nil)).Should(Equal(models.ActualLRPStateRunning))
					}
				})

				It("eventually is accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(ConsistOf([]string{"0", "1", "2"}))
				})
			})

			Context("to 0 instances", func() {
				BeforeEach(func() {
					newInstances = 0
				})

				It("eventually is not accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(BeEmpty())
				})
			})
		})
	})
})

func createDesiredLRP(processGuid string) *models.DesiredLRP {
	lrp := helpers.DefaultLRPCreateRequest(processGuid, "log-guid", 1)
	lrp.Setup = nil
	lrp.CachedDependencies = []*models.CachedDependency{{
		From:      fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "lrp.zip"),
		To:        "/tmp/diego/lrp",
		Name:      "lrp bits",
		CacheKey:  "lrp-cache-key",
		LogSource: "APP",
	}}
	lrp.LegacyDownloadUser = "vcap"
	lrp.Privileged = true
	lrp.Action = models.WrapAction(&models.RunAction{
		User: "vcap",
		Path: "/tmp/diego/lrp/go-server",
		Env:  []*models.EnvironmentVariable{{"PORT", "8080"}},
	})
	routes := cfroutes.CFRoutes{{Hostnames: []string{helpers.DefaultHost}, Port: 8080}}.RoutingInfo()
	lrp.Routes = &routes
	return lrp
}

func evacuateARep(
	processGuid string,
	logger lager.Logger,
	bbsClient bbs.InternalClient,
	cellAID, cellARepAddr string,
	cellBID, cellBRepAddr string,
) {
	By("finding rep with one instance running")
	actualLRPGroups, err := bbsClient.ActualLRPGroupsByProcessGuid(logger, processGuid)
	Expect(err).NotTo(HaveOccurred())
	Expect(actualLRPGroups).To(HaveLen(3))
	instancePerRepCount := map[string]int{}
	for _, lrpGroup := range actualLRPGroups {
		lrp, _ := lrpGroup.Resolve()
		cellID := lrp.ActualLRPInstanceKey.CellId
		instancePerRepCount[cellID]++
	}
	repWithOneInstance := ""
	for cellID, count := range instancePerRepCount {
		if count == 1 {
			repWithOneInstance = cellID
			break
		}
	}
	Expect(repWithOneInstance).NotTo(BeEmpty())

	evacuatingRepAddr := ""
	otherRepID := ""
	if repWithOneInstance == cellAID {
		evacuatingRepAddr = cellARepAddr
		otherRepID = cellBID
	} else if repWithOneInstance == cellBID {
		evacuatingRepAddr = cellBRepAddr
		otherRepID = cellAID
	} else {
		Fail(fmt.Sprintf("cell id %s doesn't match either cell-a or cell-b", repWithOneInstance))
	}

	By(fmt.Sprintf("sending evacuate request to %s", repWithOneInstance))
	resp, err := http.Post(fmt.Sprintf("http://%s/evacuate", evacuatingRepAddr), "text/html", nil)
	Expect(err).NotTo(HaveOccurred())
	resp.Body.Close()
	Expect(resp.StatusCode).To(Equal(http.StatusAccepted))

	By("waiting for the lrp to run on the new cell")
	Eventually(func() map[string]int {
		lrps := helpers.RunningActualLRPs(logger, bbsClient, processGuid)
		cellIDs := map[string]int{}
		for _, lrp := range lrps {
			cellIDs[lrp.CellId]++
		}
		return cellIDs
	}).Should(Equal(map[string]int{otherRepID: 3}))
}
<|endoftext|>


<commit_before>package cell_test

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"time"

	archive_helper "code.cloudfoundry.org/archiver/extractor/test_helper"
	"code.cloudfoundry.org/bbs"
	"code.cloudfoundry.org/bbs/models"
	"code.cloudfoundry.org/durationjson"
	"code.cloudfoundry.org/inigo/fixtures"
	"code.cloudfoundry.org/inigo/helpers"
	"code.cloudfoundry.org/lager"
	repconfig "code.cloudfoundry.org/rep/cmd/rep/config"
	routeemitterconfig "code.cloudfoundry.org/route-emitter/cmd/route-emitter/config"
	"code.cloudfoundry.org/routing-info/cfroutes"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
	"github.com/tedsuo/ifrit/grouper"
)

var _ = Describe("LocalRouteEmitter", func() {
	var (
		processGuid                                  string
		runtime                                      ifrit.Process
		archiveFiles                                 []archive_helper.ArchiveFile
		fileServerStaticDir                          string
		cellAID, cellBID, cellARepAddr, cellBRepAddr string
	)

	BeforeEach(func() {
		processGuid = helpers.GenerateGuid()

		var fileServer ifrit.Runner
		fileServer, fileServerStaticDir = componentMaker.FileServer()

		cellAID = "cell-a"
		cellBID = "cell-b"

		cellARepAddr = fmt.Sprintf("0.0.0.0:%d", 14200+GinkgoParallelNode())
		repA := componentMaker.RepN(1, func(config *repconfig.RepConfig) {
			config.CellID = cellAID
			config.ListenAddr = cellARepAddr
			config.EvacuationTimeout = durationjson.Duration(30 * time.Second)
		})

		cellBRepAddr = fmt.Sprintf("0.0.0.0:%d", 14400+GinkgoParallelNode())
		repB := componentMaker.RepN(2, func(config *repconfig.RepConfig) {
			config.CellID = cellBID
			config.ListenAddr = cellBRepAddr
			config.EvacuationTimeout = durationjson.Duration(30 * time.Second)
		})

		runtime = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"router", componentMaker.Router()},
			{"file-server", fileServer},
			{"rep-1", repA},
			{"rep-2", repB},
			{"auctioneer", componentMaker.Auctioneer()},
			// override syncinterval and cell id
			{"route-emitter-1", componentMaker.RouteEmitterN(1, func(config *routeemitterconfig.RouteEmitterConfig) {
				config.SyncInterval = durationjson.Duration(time.Hour)
				config.CellID = cellAID
			})},
			{"route-emitter-2", componentMaker.RouteEmitterN(1, func(config *routeemitterconfig.RouteEmitterConfig) {
				config.SyncInterval = durationjson.Duration(time.Hour)
				config.CellID = cellBID
			})},
		}))
		archiveFiles = fixtures.GoServerApp()
	})

	AfterEach(func() {
		helpers.StopProcesses(runtime)
	})

	JustBeforeEach(func() {
		archive_helper.CreateZipArchive(
			filepath.Join(fileServerStaticDir, "lrp.zip"),
			archiveFiles,
		)
	})

	Describe("desiring", func() {
		var (
			lrp       *models.DesiredLRP
			instances int32
		)

		BeforeEach(func() {
			instances = 1
		})

		JustBeforeEach(func() {
			lrp = createDesiredLRP(processGuid)
			lrp.Instances = instances
			err := bbsClient.DesireLRP(logger, lrp)
			Expect(err).NotTo(HaveOccurred())
			Eventually(helpers.LRPStatePoller(logger, bbsClient, processGuid, nil)).Should(Equal(models.ActualLRPStateRunning))
		})

		It("eventually is accessible through the router within a second", func() {
			Eventually(
				helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, helpers.DefaultHost),
				time.Second,
				10*time.Millisecond,
			).Should(Equal(http.StatusOK))
		})

		Context("when there are 3 instances", func() {
			BeforeEach(func() {
				instances = 3
			})

			Context("and a rep start evacuating", func() {
				JustBeforeEach(func() {
					evacuateARep(
						processGuid,
						logger,
						bbsClient,
						cellAID, cellARepAddr,
						cellBID, cellBRepAddr,
					)
				})

				It("eventually should make the new lrp routable within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(ConsistOf([]string{"0", "1", "2"}))
				})
			})

			Context("and the app is deleted", func() {
				JustBeforeEach(func() {
					err := bbsClient.RemoveDesiredLRP(logger, processGuid)
					Expect(err).NotTo(HaveOccurred())
				})

				It("eventually is not accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(BeEmpty())
				})
			})

			Context("and the app is updated", func() {
				var (
					desiredLRPUdate *models.DesiredLRPUpdate
				)

				BeforeEach(func() {
					desiredLRPUdate = &models.DesiredLRPUpdate{}
				})

				JustBeforeEach(func() {
					err := bbsClient.UpdateDesiredLRP(logger, processGuid, desiredLRPUdate)
					Expect(err).NotTo(HaveOccurred())
				})

				Context("to scale the app down", func() {
					BeforeEach(func() {
						newInstances := int32(1)
						desiredLRPUdate.Instances = &newInstances
					})

					It("eventually extra routes are removed within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
							time.Second,
							10*time.Millisecond,
						).Should(ConsistOf([]string{"0"}))
					})
				})

				Context("to add new route", func() {
					BeforeEach(func() {
						routes := cfroutes.CFRoutes{{Hostnames: []string{helpers.DefaultHost, "some-other-route"}, Port: 8080}}.RoutingInfo()
						desiredLRPUdate.Routes = &routes
					})

					It("eventually is accessible using the new route within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, "some-other-route"),
							time.Second,
							10*time.Millisecond,
						).Should(ConsistOf([]string{"0", "1", "2"}))
					})
				})

				Context("and all routes are deleted", func() {
					BeforeEach(func() {
						routes := cfroutes.CFRoutes{}.RoutingInfo()
						desiredLRPUdate.Routes = &routes
					})

					It("eventually not accessible using its route within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
							time.Second,
							10*time.Millisecond,
						).Should(BeEmpty())
					})
				})
			})
		})

		Context("when the instances count change", func() {
			var (
				newInstances int32
			)

			JustBeforeEach(func() {
				err := bbsClient.UpdateDesiredLRP(logger, processGuid, &models.DesiredLRPUpdate{
					Instances: &newInstances,
				})
				Expect(err).NotTo(HaveOccurred())
			})

			Context("to 3 instances", func() {
				BeforeEach(func() {
					newInstances = 3
				})

				JustBeforeEach(func() {
					for i := 1; i < int(newInstances); i++ {
						Eventually(helpers.LRPInstanceStatePoller(logger, bbsClient, processGuid, i, nil)).Should(Equal(models.ActualLRPStateRunning))
					}
				})

				It("eventually is accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(ConsistOf([]string{"0", "1", "2"}))
				})
			})

			Context("to 0 instances", func() {
				BeforeEach(func() {
					newInstances = 0
				})

				It("eventually is not accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(BeEmpty())
				})
			})
		})
	})
})

func createDesiredLRP(processGuid string) *models.DesiredLRP {
	lrp := helpers.DefaultLRPCreateRequest(processGuid, "log-guid", 1)
	lrp.Setup = nil
	lrp.CachedDependencies = []*models.CachedDependency{{
		From:      fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "lrp.zip"),
		To:        "/tmp/diego/lrp",
		Name:      "lrp bits",
		CacheKey:  "lrp-cache-key",
		LogSource: "APP",
	}}
	lrp.LegacyDownloadUser = "vcap"
	lrp.Privileged = true
	lrp.Action = models.WrapAction(&models.RunAction{
		User: "vcap",
		Path: "/tmp/diego/lrp/go-server",
		Env:  []*models.EnvironmentVariable{{"PORT", "8080"}},
	})
	routes := cfroutes.CFRoutes{{Hostnames: []string{helpers.DefaultHost}, Port: 8080}}.RoutingInfo()
	lrp.Routes = &routes
	return lrp
}

func evacuateARep(
	processGuid string,
	logger lager.Logger,
	bbsClient bbs.InternalClient,
	cellAID, cellARepAddr string,
	cellBID, cellBRepAddr string,
) {
	By("finding rep with one instance running")
	actualLRPGroups, err := bbsClient.ActualLRPGroupsByProcessGuid(logger, processGuid)
	Expect(err).NotTo(HaveOccurred())
	Expect(actualLRPGroups).To(HaveLen(3))
	instancePerRepCount := map[string]int{}
	for _, lrpGroup := range actualLRPGroups {
		lrp, _ := lrpGroup.Resolve()
		cellID := lrp.ActualLRPInstanceKey.CellId
		instancePerRepCount[cellID]++
	}
	repWithOneInstance := ""
	for cellID, count := range instancePerRepCount {
		if count == 1 {
			repWithOneInstance = cellID
			break
		}
	}
	Expect(repWithOneInstance).NotTo(BeEmpty())

	evacuatingRepAddr := ""
	otherRepID := ""
	if repWithOneInstance == cellAID {
		evacuatingRepAddr = cellARepAddr
		otherRepID = cellBID
	} else if repWithOneInstance == cellBID {
		evacuatingRepAddr = cellBRepAddr
		otherRepID = cellAID
	} else {
		Fail(fmt.Sprintf("cell id %s doesn't match either cell-a or cell-b", repWithOneInstance))
	}

	By(fmt.Sprintf("sending evacuate request to %s", repWithOneInstance))
	resp, err := http.Post(fmt.Sprintf("http://%s/evacuate", evacuatingRepAddr), "text/html", nil)
	Expect(err).NotTo(HaveOccurred())
	resp.Body.Close()
	Expect(resp.StatusCode).To(Equal(http.StatusAccepted))

	By("waiting for the lrp to run on the new cell")
	Eventually(func() map[string]int {
		lrps := helpers.ActiveActualLRPs(logger, bbsClient, processGuid)
		cellIDs := map[string]int{}
		for _, lrp := range lrps {
			cellIDs[lrp.CellId]++
		}
		return cellIDs
	}).Should(Equal(map[string]int{otherRepID: 3}))
}
<commit_msg>Make the two local route emitters have different session names<commit_after>package cell_test

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"time"

	archive_helper "code.cloudfoundry.org/archiver/extractor/test_helper"
	"code.cloudfoundry.org/bbs"
	"code.cloudfoundry.org/bbs/models"
	"code.cloudfoundry.org/durationjson"
	"code.cloudfoundry.org/inigo/fixtures"
	"code.cloudfoundry.org/inigo/helpers"
	"code.cloudfoundry.org/lager"
	repconfig "code.cloudfoundry.org/rep/cmd/rep/config"
	routeemitterconfig "code.cloudfoundry.org/route-emitter/cmd/route-emitter/config"
	"code.cloudfoundry.org/routing-info/cfroutes"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
	"github.com/tedsuo/ifrit/grouper"
)

var _ = Describe("LocalRouteEmitter", func() {
	var (
		processGuid                                  string
		runtime                                      ifrit.Process
		archiveFiles                                 []archive_helper.ArchiveFile
		fileServerStaticDir                          string
		cellAID, cellBID, cellARepAddr, cellBRepAddr string
	)

	BeforeEach(func() {
		processGuid = helpers.GenerateGuid()

		var fileServer ifrit.Runner
		fileServer, fileServerStaticDir = componentMaker.FileServer()

		cellAID = "cell-a"
		cellBID = "cell-b"

		cellARepAddr = fmt.Sprintf("0.0.0.0:%d", 14200+GinkgoParallelNode())
		repA := componentMaker.RepN(1, func(config *repconfig.RepConfig) {
			config.CellID = cellAID
			config.ListenAddr = cellARepAddr
			config.EvacuationTimeout = durationjson.Duration(30 * time.Second)
		})

		cellBRepAddr = fmt.Sprintf("0.0.0.0:%d", 14400+GinkgoParallelNode())
		repB := componentMaker.RepN(2, func(config *repconfig.RepConfig) {
			config.CellID = cellBID
			config.ListenAddr = cellBRepAddr
			config.EvacuationTimeout = durationjson.Duration(30 * time.Second)
		})

		runtime = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"router", componentMaker.Router()},
			{"file-server", fileServer},
			{"rep-1", repA},
			{"rep-2", repB},
			{"auctioneer", componentMaker.Auctioneer()},
			// override syncinterval and cell id
			{"route-emitter-1", componentMaker.RouteEmitterN(1, func(config *routeemitterconfig.RouteEmitterConfig) {
				config.SyncInterval = durationjson.Duration(time.Hour)
				config.CellID = cellAID
			})},
			{"route-emitter-2", componentMaker.RouteEmitterN(2, func(config *routeemitterconfig.RouteEmitterConfig) {
				config.SyncInterval = durationjson.Duration(time.Hour)
				config.CellID = cellBID
			})},
		}))
		archiveFiles = fixtures.GoServerApp()
	})

	AfterEach(func() {
		helpers.StopProcesses(runtime)
	})

	JustBeforeEach(func() {
		archive_helper.CreateZipArchive(
			filepath.Join(fileServerStaticDir, "lrp.zip"),
			archiveFiles,
		)
	})

	Describe("desiring", func() {
		var (
			lrp       *models.DesiredLRP
			instances int32
		)

		BeforeEach(func() {
			instances = 1
		})

		JustBeforeEach(func() {
			lrp = createDesiredLRP(processGuid)
			lrp.Instances = instances
			err := bbsClient.DesireLRP(logger, lrp)
			Expect(err).NotTo(HaveOccurred())
			Eventually(helpers.LRPStatePoller(logger, bbsClient, processGuid, nil)).Should(Equal(models.ActualLRPStateRunning))
		})

		It("eventually is accessible through the router within a second", func() {
			Eventually(
				helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, helpers.DefaultHost),
				time.Second,
				10*time.Millisecond,
			).Should(Equal(http.StatusOK))
		})

		Context("when there are 3 instances", func() {
			BeforeEach(func() {
				instances = 3
			})

			Context("and a rep start evacuating", func() {
				JustBeforeEach(func() {
					evacuateARep(
						processGuid,
						logger,
						bbsClient,
						cellAID, cellARepAddr,
						cellBID, cellBRepAddr,
					)
				})

				It("eventually should make the new lrp routable within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(ConsistOf([]string{"0", "1", "2"}))
				})
			})

			Context("and the app is deleted", func() {
				JustBeforeEach(func() {
					err := bbsClient.RemoveDesiredLRP(logger, processGuid)
					Expect(err).NotTo(HaveOccurred())
				})

				It("eventually is not accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(BeEmpty())
				})
			})

			Context("and the app is updated", func() {
				var (
					desiredLRPUdate *models.DesiredLRPUpdate
				)

				BeforeEach(func() {
					desiredLRPUdate = &models.DesiredLRPUpdate{}
				})

				JustBeforeEach(func() {
					err := bbsClient.UpdateDesiredLRP(logger, processGuid, desiredLRPUdate)
					Expect(err).NotTo(HaveOccurred())
				})

				Context("to scale the app down", func() {
					BeforeEach(func() {
						newInstances := int32(1)
						desiredLRPUdate.Instances = &newInstances
					})

					It("eventually extra routes are removed within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
							time.Second,
							10*time.Millisecond,
						).Should(ConsistOf([]string{"0"}))
					})
				})

				Context("to add new route", func() {
					BeforeEach(func() {
						routes := cfroutes.CFRoutes{{Hostnames: []string{helpers.DefaultHost, "some-other-route"}, Port: 8080}}.RoutingInfo()
						desiredLRPUdate.Routes = &routes
					})

					It("eventually is accessible using the new route within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, "some-other-route"),
							time.Second,
							10*time.Millisecond,
						).Should(ConsistOf([]string{"0", "1", "2"}))
					})
				})

				Context("and all routes are deleted", func() {
					BeforeEach(func() {
						routes := cfroutes.CFRoutes{}.RoutingInfo()
						desiredLRPUdate.Routes = &routes
					})

					It("eventually not accessible using its route within a second", func() {
						Eventually(
							helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
							time.Second,
							10*time.Millisecond,
						).Should(BeEmpty())
					})
				})
			})
		})

		Context("when the instances count change", func() {
			var (
				newInstances int32
			)

			JustBeforeEach(func() {
				err := bbsClient.UpdateDesiredLRP(logger, processGuid, &models.DesiredLRPUpdate{
					Instances: &newInstances,
				})
				Expect(err).NotTo(HaveOccurred())
			})

			Context("to 3 instances", func() {
				BeforeEach(func() {
					newInstances = 3
				})

				JustBeforeEach(func() {
					for i := 1; i < int(newInstances); i++ {
						Eventually(helpers.LRPInstanceStatePoller(logger, bbsClient, processGuid, i, nil)).Should(Equal(models.ActualLRPStateRunning))
					}
				})

				It("eventually is accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(ConsistOf([]string{"0", "1", "2"}))
				})
			})

			Context("to 0 instances", func() {
				BeforeEach(func() {
					newInstances = 0
				})

				It("eventually is not accessible through the router within a second", func() {
					Eventually(
						helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, helpers.DefaultHost),
						time.Second,
						10*time.Millisecond,
					).Should(BeEmpty())
				})
			})
		})
	})
})

func createDesiredLRP(processGuid string) *models.DesiredLRP {
	lrp := helpers.DefaultLRPCreateRequest(processGuid, "log-guid", 1)
	lrp.Setup = nil
	lrp.CachedDependencies = []*models.CachedDependency{{
		From:      fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "lrp.zip"),
		To:        "/tmp/diego/lrp",
		Name:      "lrp bits",
		CacheKey:  "lrp-cache-key",
		LogSource: "APP",
	}}
	lrp.LegacyDownloadUser = "vcap"
	lrp.Privileged = true
	lrp.Action = models.WrapAction(&models.RunAction{
		User: "vcap",
		Path: "/tmp/diego/lrp/go-server",
		Env:  []*models.EnvironmentVariable{{"PORT", "8080"}},
	})
	routes := cfroutes.CFRoutes{{Hostnames: []string{helpers.DefaultHost}, Port: 8080}}.RoutingInfo()
	lrp.Routes = &routes
	return lrp
}

func evacuateARep(
	processGuid string,
	logger lager.Logger,
	bbsClient bbs.InternalClient,
	cellAID, cellARepAddr string,
	cellBID, cellBRepAddr string,
) {
	By("finding rep with one instance running")
	actualLRPGroups, err := bbsClient.ActualLRPGroupsByProcessGuid(logger, processGuid)
	Expect(err).NotTo(HaveOccurred())
	Expect(actualLRPGroups).To(HaveLen(3))
	instancePerRepCount := map[string]int{}
	for _, lrpGroup := range actualLRPGroups {
		lrp, _ := lrpGroup.Resolve()
		cellID := lrp.ActualLRPInstanceKey.CellId
		instancePerRepCount[cellID]++
	}
	repWithOneInstance := ""
	for cellID, count := range instancePerRepCount {
		if count == 1 {
			repWithOneInstance = cellID
			break
		}
	}
	Expect(repWithOneInstance).NotTo(BeEmpty())

	evacuatingRepAddr := ""
	otherRepID := ""
	if repWithOneInstance == cellAID {
		evacuatingRepAddr = cellARepAddr
		otherRepID = cellBID
	} else if repWithOneInstance == cellBID {
		evacuatingRepAddr = cellBRepAddr
		otherRepID = cellAID
	} else {
		Fail(fmt.Sprintf("cell id %s doesn't match either cell-a or cell-b", repWithOneInstance))
	}

	By(fmt.Sprintf("sending evacuate request to %s", repWithOneInstance))
	resp, err := http.Post(fmt.Sprintf("http://%s/evacuate", evacuatingRepAddr), "text/html", nil)
	Expect(err).NotTo(HaveOccurred())
	resp.Body.Close()
	Expect(resp.StatusCode).To(Equal(http.StatusAccepted))

	By("waiting for the lrp to run on the new cell")
	Eventually(func() map[string]int {
		lrps := helpers.ActiveActualLRPs(logger, bbsClient, processGuid)
		cellIDs := map[string]int{}
		for _, lrp := range lrps {
			cellIDs[lrp.CellId]++
		}
		return cellIDs
	}).Should(Equal(map[string]int{otherRepID: 3}))
}
<|endoftext|>


<commit_before>package xml

import (
	"bytes"
	"github.com/crabmusket/gosunspec"
	"github.com/crabmusket/gosunspec/memory"
	"github.com/crabmusket/gosunspec/models/model1"
	"github.com/crabmusket/gosunspec/models/model101"
	"testing"
)

func TestCopyDevice(t *testing.T) {
	arr, _ := memory.Open(memory.TwoDeviceSlab)
	arr.Do(func(d sunspec.Device) {
		d.Do(func(m sunspec.Model) {
			m.Do(func(b sunspec.Block) {
				_ = b.Read()
			})
		})
	})
	_, cloneX := CopyArray(arr)
	// bytes, _ := xml.MarshalIndent(cloneX, "", "    ")
	// fmt.Printf("%s\n", string(bytes))
	arr2, err := Open(cloneX)
	if err != nil {
		t.Fatal(err)
	}
	count := 0
	arr2.Do(func(d sunspec.Device) {
		d.Do(func(m sunspec.Model) {
			m.Do(func(b sunspec.Block) {
				if err := b.Read(); err != nil {
					t.Fatal(err)
				}
				b.Do(func(p sunspec.Point) {
					if err := p.Error(); err != nil {
						t.Fatalf("p has error. model=%d, point=%s\n", m.Id(), p.Id())
					}

					if v := p.Value(); v != memory.ExpectedValues[p.Type()] {
						t.Fatalf("unexpected value. model=%d, point=%s. actual=%#v, expected=%#v. type=%s", m.Id(), p.Id(), v, memory.ExpectedValues[p.Type()], p.Type())
					}
					count++
				})
			})
		})
	})

	expected := 432
	if count != expected {
		t.Fatalf("unexpected number of points. actual: %d, expected: %d", count, expected)
	}
}

const inconsistentXML = `
<SunSpecData v="1">
	<d>
		<m id="101">

31

		</m>
		<m id="101">

31

-1

		</m>
	</d>
</SunSpecData>
`

func TestInconsistentXML(t *testing.T) {
	data, err := parseXML(bytes.NewBuffer([]byte(inconsistentXML)))
	if err != nil {
		t.Fatal(err)
	}
	arr, err := Open(data)
	if err != nil {
		t.Fatal(err)
	}
	arr.Do(func(d sunspec.Device) {
		d.Do(func(m sunspec.Model) {
			if m.Id() != model101.ModelID {
				return
			}
			b := m.MustBlock(0)
			if err := b.Read(model101.A); err != nil {
				// t.Fatal(err)
			}
			p := b.MustPoint("A")
			if p.Error() != nil {
				t.Fatal(p.Error())
			}
			v := p.ScaledValue()
			expected := 3100.0
			if v != expected {
				t.Fatalf("actual: %0f, expected: %0f", v, expected)
			}
		})
	})
}

const implicitModel1XML = `
<SunSpecData v="1">
	<d man="ACME" mod="Model X" sn="000001">
	</d>
</SunSpecData>
`

func TestImplicitModel1(t *testing.T) {
	data, err := parseXML(bytes.NewBuffer([]byte(implicitModel1XML)))
	if err != nil {
		t.Fatal(err)
	}
	arr, err := Open(data)
	if err != nil {
		t.Fatal(err)
	}
	arr.Do(func(d sunspec.Device) {
		d.MustModel(model1.ModelID)
		d.Do(func(m sunspec.Model) {
			b := m.MustBlock(0)
			if err := b.Read(model1.SN, model1.Md, model1.Mn); err != nil {
				t.Fatal(err)
			}

			// read from implicit model 1 element and see device element contents

			expected := "ACME"
			actual := b.MustPoint(model1.Mn).StringValue()
			if expected != actual {
				t.Fatalf("Mn. actual=%s, expected=%s\n", actual, expected)
			}
			expected = "Model X"
			actual = b.MustPoint(model1.Md).StringValue()
			if expected != actual {
				t.Fatalf("Md. actual=%s, expected=%s\n", actual, expected)
			}
			expected = "000001"
			actual = b.MustPoint(model1.SN).StringValue()
			if expected != actual {
				t.Fatalf("SN. actual=%s, expected=%s\n", actual, expected)
			}

			// write into implicit model 1 element and see results in device element

			b.MustPoint(model1.Mn).SetStringValue("Road Runner")
			b.Write(model1.Mn)
			expected = "Road Runner"
			actual = data.Devices[0].Manufacturer
			if expected != actual {
				t.Fatalf("Man. actual=%s, expected=%s\n", actual, expected)
			}

			b.MustPoint(model1.Md).SetStringValue("Model XX")
			b.Write(model1.Md)
			expected = "Model XX"
			actual = data.Devices[0].Model
			if expected != actual {
				t.Fatalf("Model. actual=%s, expected=%s\n", actual, expected)
			}

			b.MustPoint(model1.SN).SetStringValue("000002")
			b.Write(model1.SN)
			expected = "000002"
			actual = data.Devices[0].Serial
			if expected != actual {
				t.Fatalf("Model. actual=%s, expected=%s\n", actual, expected)
			}

			{
				actual := len(data.Devices[0].Models)
				expected := 0
				if expected != actual {
					t.Fatalf("#models. actual=%d, expected=%d\n", actual, expected)
				}
			}

			b.MustPoint(model1.Opt).SetStringValue("OPT")
			b.Write(model1.Opt)

			{
				actual := len(data.Devices[0].Models)
				expected := 1
				if expected != actual {
					t.Fatalf("#models (after write to opt). actual=%d, expected=%d\n", actual, expected)
				}
			}

		})
	})

}

func TestXmlOpen(t *testing.T) {
	buffer := bytes.NewBuffer([]byte(example))
	data, err := parseXML(buffer)
	if err != nil {
		t.Fatal("could not parse example", err.Error())
	}
	if x, err := Open(data); err != nil {
		t.Fatal(err)
	} else {
		x.Do(func(d sunspec.Device) {
			d.Do(func(m sunspec.Model) {
				m.Do(func(b sunspec.Block) {
					_ = b.Read()
				})
			})
		})
		c, cx := CopyArray(x)
		c.Do(func(d sunspec.Device) {
			_ = d.MustModel(101)
		})
		if cx.Devices[0].Models[1].Id != 101 {
			t.Fatalf("unexpected id found")
		}
		if len(cx.Devices[0].Models[1].Points) != 15 {
			t.Fatalf("not enough points: %d", len(cx.Devices[0].Models[1].Points))
		}
	}
}
<commit_msg>test: don't hide errors, except for the boring ones.<commit_after>package xml

import (
	"bytes"
	"github.com/crabmusket/gosunspec"
	"github.com/crabmusket/gosunspec/memory"
	"github.com/crabmusket/gosunspec/models/model1"
	"github.com/crabmusket/gosunspec/models/model101"
	"testing"
)

func TestCopyDevice(t *testing.T) {
	arr, _ := memory.Open(memory.TwoDeviceSlab)
	arr.Do(func(d sunspec.Device) {
		d.Do(func(m sunspec.Model) {
			m.Do(func(b sunspec.Block) {
				_ = b.Read()
			})
		})
	})
	_, cloneX := CopyArray(arr)
	// bytes, _ := xml.MarshalIndent(cloneX, "", "    ")
	// fmt.Printf("%s\n", string(bytes))
	arr2, err := Open(cloneX)
	if err != nil {
		t.Fatal(err)
	}
	count := 0
	arr2.Do(func(d sunspec.Device) {
		d.Do(func(m sunspec.Model) {
			m.Do(func(b sunspec.Block) {
				if err := b.Read(); err != nil {
					t.Fatal(err)
				}
				b.Do(func(p sunspec.Point) {
					if err := p.Error(); err != nil {
						t.Fatalf("p has error. model=%d, point=%s\n", m.Id(), p.Id())
					}

					if v := p.Value(); v != memory.ExpectedValues[p.Type()] {
						t.Fatalf("unexpected value. model=%d, point=%s. actual=%#v, expected=%#v. type=%s", m.Id(), p.Id(), v, memory.ExpectedValues[p.Type()], p.Type())
					}
					count++
				})
			})
		})
	})

	expected := 432
	if count != expected {
		t.Fatalf("unexpected number of points. actual: %d, expected: %d", count, expected)
	}
}

const inconsistentXML = `
<SunSpecData v="1">
	<d>
		<m id="101">

31

		</m>
		<m id="101">

31

-1

		</m>
	</d>
</SunSpecData>
`

func TestInconsistentXML(t *testing.T) {
	data, err := parseXML(bytes.NewBuffer([]byte(inconsistentXML)))
	if err != nil {
		t.Fatal(err)
	}
	arr, err := Open(data)
	if err != nil {
		t.Fatal(err)
	}
	arr.Do(func(d sunspec.Device) {
		d.Do(func(m sunspec.Model) {
			if m.Id() != model101.ModelID {
				return
			}
			b := m.MustBlock(0)
			if err := b.Read(model101.A); err != nil {
				// t.Fatal(err)
			}
			p := b.MustPoint("A")
			if p.Error() != nil {
				t.Fatal(p.Error())
			}
			v := p.ScaledValue()
			expected := 3100.0
			if v != expected {
				t.Fatalf("actual: %0f, expected: %0f", v, expected)
			}
		})
	})
}

const implicitModel1XML = `
<SunSpecData v="1">
	<d man="ACME" mod="Model X" sn="000001">
	</d>
</SunSpecData>
`

func TestImplicitModel1(t *testing.T) {
	data, err := parseXML(bytes.NewBuffer([]byte(implicitModel1XML)))
	if err != nil {
		t.Fatal(err)
	}
	arr, err := Open(data)
	if err != nil {
		t.Fatal(err)
	}
	arr.Do(func(d sunspec.Device) {
		d.MustModel(model1.ModelID)
		d.Do(func(m sunspec.Model) {
			b := m.MustBlock(0)
			if err := b.Read(model1.SN, model1.Md, model1.Mn); err != nil {
				t.Fatal(err)
			}

			// read from implicit model 1 element and see device element contents

			expected := "ACME"
			actual := b.MustPoint(model1.Mn).StringValue()
			if expected != actual {
				t.Fatalf("Mn. actual=%s, expected=%s\n", actual, expected)
			}
			expected = "Model X"
			actual = b.MustPoint(model1.Md).StringValue()
			if expected != actual {
				t.Fatalf("Md. actual=%s, expected=%s\n", actual, expected)
			}
			expected = "000001"
			actual = b.MustPoint(model1.SN).StringValue()
			if expected != actual {
				t.Fatalf("SN. actual=%s, expected=%s\n", actual, expected)
			}

			// write into implicit model 1 element and see results in device element

			b.MustPoint(model1.Mn).SetStringValue("Road Runner")
			b.Write(model1.Mn)
			expected = "Road Runner"
			actual = data.Devices[0].Manufacturer
			if expected != actual {
				t.Fatalf("Man. actual=%s, expected=%s\n", actual, expected)
			}

			b.MustPoint(model1.Md).SetStringValue("Model XX")
			b.Write(model1.Md)
			expected = "Model XX"
			actual = data.Devices[0].Model
			if expected != actual {
				t.Fatalf("Model. actual=%s, expected=%s\n", actual, expected)
			}

			b.MustPoint(model1.SN).SetStringValue("000002")
			b.Write(model1.SN)
			expected = "000002"
			actual = data.Devices[0].Serial
			if expected != actual {
				t.Fatalf("Model. actual=%s, expected=%s\n", actual, expected)
			}

			{
				actual := len(data.Devices[0].Models)
				expected := 0
				if expected != actual {
					t.Fatalf("#models. actual=%d, expected=%d\n", actual, expected)
				}
			}

			b.MustPoint(model1.Opt).SetStringValue("OPT")
			b.Write(model1.Opt)

			{
				actual := len(data.Devices[0].Models)
				expected := 1
				if expected != actual {
					t.Fatalf("#models (after write to opt). actual=%d, expected=%d\n", actual, expected)
				}
			}

		})
	})

}

func TestXmlOpen(t *testing.T) {
	buffer := bytes.NewBuffer([]byte(example))
	data, err := parseXML(buffer)
	if err != nil {
		t.Fatal("could not parse example", err.Error())
	}
	if x, err := Open(data); err != nil {
		t.Fatal(err)
	} else {
		x.Do(func(d sunspec.Device) {
			d.Do(func(m sunspec.Model) {
				m.Do(func(b sunspec.Block) {
					err = b.Read()
					if err != ErrNoSuchElement {
						t.Fatal(err)
					}
				})
			})
		})
		c, cx := CopyArray(x)
		c.Do(func(d sunspec.Device) {
			_ = d.MustModel(101)
		})
		if cx.Devices[0].Models[1].Id != 101 {
			t.Fatalf("unexpected id found")
		}
		if len(cx.Devices[0].Models[1].Points) != 15 {
			t.Fatalf("not enough points: %d", len(cx.Devices[0].Models[1].Points))
		}
	}
}
<|endoftext|>


<commit_before>package xmmsclient

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"sync/atomic"
)

type context struct {
	result     chan XmmsValue
	sequenceNr uint32
	broadcast  bool
}

type header struct {
	objectId   uint32
	commandId  uint32
	sequenceNr uint32
	length     uint32
}

type message struct {
	objectId  uint32
	commandId uint32
	args      XmmsValue
	context   context
}

type reply struct {
	sequenceNr uint32
	value      XmmsValue
}

type Client struct {
	conn       io.ReadWriteCloser
	sequenceNr uint32
	clientId   XmmsValue // TODO: uint32

	inbound  chan reply
	outbound chan message
	registry chan context
}

func parseHeader(buf *bytes.Buffer) (*header, error) {
	var hdr header

	err := binary.Read(buf, binary.BigEndian, &hdr.objectId)
	if err != nil {
		return nil, err
	}

	err = binary.Read(buf, binary.BigEndian, &hdr.commandId)
	if err != nil {
		return nil, err
	}

	err = binary.Read(buf, binary.BigEndian, &hdr.sequenceNr)
	if err != nil {
		return nil, err
	}

	err = binary.Read(buf, binary.BigEndian, &hdr.length)
	if err != nil {
		return nil, err
	}

	return &hdr, nil
}

func writeHeader(w io.ReadWriteCloser, hdr *header) error {
	err := binary.Write(w, binary.BigEndian, hdr.objectId)
	if err != nil {
		return err
	}

	err = binary.Write(w, binary.BigEndian, hdr.commandId)
	if err != nil {
		return err
	}

	err = binary.Write(w, binary.BigEndian, hdr.sequenceNr)
	if err != nil {
		return err
	}

	err = binary.Write(w, binary.BigEndian, hdr.length)
	if err != nil {
		return err
	}

	return nil
}

func (c *Client) nextSequenceNr() uint32 {
	return atomic.AddUint32(&c.sequenceNr, 1)
}

func (c *Client) reader() {
	var buffer = make([]byte, 16)
	for {
		read, err := io.ReadFull(c.conn, buffer)
		if err != nil {
			// TODO: Post to error channel
			fmt.Println("error reading socket")
			continue
		}

		if read != len(buffer) {
			fmt.Println("nothing to read")
			continue
		}

		header, err := parseHeader(bytes.NewBuffer(buffer))

		payload := make([]byte, header.length)
		c.conn.Read(payload)
		value, _ := DeserializeXmmsValue(bytes.NewBuffer(payload))
		c.inbound <- reply{header.sequenceNr, value}
	}
}

func (c *Client) writer() {
	for {
		select {
		case msg := <-c.outbound:
			var payload bytes.Buffer

			c.registry <- msg.context

			err := SerializeXmmsValue(msg.args, &payload)
			if err != nil {
				continue
			}

			header := header{
				objectId:   msg.objectId,
				commandId:  msg.commandId,
				sequenceNr: msg.context.sequenceNr,
				length:     uint32(len(payload.Bytes())),
			}

			err = writeHeader(c.conn, &header)
			if err != nil {
				continue
			}

			payload.WriteTo(c.conn)
			if err != nil {
				continue
			}
		}
	}
}

func (c *Client) router() {
	var registry = make(map[uint32](context))

	for {
		select {
		case ctx := <-c.registry:
			registry[ctx.sequenceNr] = ctx
		case reply := <-c.inbound:
			ctx := registry[reply.sequenceNr]
			ctx.result <- reply.value
			if !ctx.broadcast {
				delete(registry, ctx.sequenceNr)
			}
		}
	}
}

// TODO: Needs a better home, should be generated.
func (c *Client) MainHello(client_name string) XmmsValue {
	context := context{
		result:     make(chan XmmsValue),
		sequenceNr: c.nextSequenceNr()}
	c.outbound <- message{
		objectId:  ObjectMain,
		commandId: CommandMainHello,
		context:   context,
		args:      NewXmmsList(XmmsInt(IpcVersion), XmmsString(client_name))}
	return <-context.result
}

// TODO: Needs a better home, should be generated.
func (c *Client) MainListPlugins() XmmsValue {
	context := context{
		result:     make(chan XmmsValue),
		sequenceNr: c.nextSequenceNr()}
	c.outbound <- message{
		args:      NewXmmsList(XmmsInt(0)),
		objectId:  ObjectMain,
		commandId: CommandMainListPlugins,
		context:   context}
	return <-context.result
}

// TODO: Probably something else that creates a new Client rather than Dial.
func Dial(url string, name string) (*Client, error) {
	addr, err := net.ResolveTCPAddr("tcp", url)
	if err != nil {
		return nil, err
	}

	conn, err := net.DialTCP("tcp", nil, addr)
	if err != nil {
		return nil, err
	}

	client := Client{
		conn:     conn,
		inbound:  make(chan reply),
		outbound: make(chan message),
		registry: make(chan context),
	}

	go client.router()
	go client.reader()
	go client.writer()

	client.clientId = client.MainHello(name)

	return &client, nil
}
<commit_msg>Consume outbound chan via for loop.<commit_after>package xmmsclient

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"sync/atomic"
)

type context struct {
	result     chan XmmsValue
	sequenceNr uint32
	broadcast  bool
}

type header struct {
	objectId   uint32
	commandId  uint32
	sequenceNr uint32
	length     uint32
}

type message struct {
	objectId  uint32
	commandId uint32
	args      XmmsValue
	context   context
}

type reply struct {
	sequenceNr uint32
	value      XmmsValue
}

type Client struct {
	conn       io.ReadWriteCloser
	sequenceNr uint32
	clientId   XmmsValue // TODO: uint32

	inbound  chan reply
	outbound chan message
	registry chan context
}

func parseHeader(buf *bytes.Buffer) (*header, error) {
	var hdr header

	err := binary.Read(buf, binary.BigEndian, &hdr.objectId)
	if err != nil {
		return nil, err
	}

	err = binary.Read(buf, binary.BigEndian, &hdr.commandId)
	if err != nil {
		return nil, err
	}

	err = binary.Read(buf, binary.BigEndian, &hdr.sequenceNr)
	if err != nil {
		return nil, err
	}

	err = binary.Read(buf, binary.BigEndian, &hdr.length)
	if err != nil {
		return nil, err
	}

	return &hdr, nil
}

func writeHeader(w io.ReadWriteCloser, hdr *header) error {
	err := binary.Write(w, binary.BigEndian, hdr.objectId)
	if err != nil {
		return err
	}

	err = binary.Write(w, binary.BigEndian, hdr.commandId)
	if err != nil {
		return err
	}

	err = binary.Write(w, binary.BigEndian, hdr.sequenceNr)
	if err != nil {
		return err
	}

	err = binary.Write(w, binary.BigEndian, hdr.length)
	if err != nil {
		return err
	}

	return nil
}

func (c *Client) nextSequenceNr() uint32 {
	return atomic.AddUint32(&c.sequenceNr, 1)
}

func (c *Client) reader() {
	var buffer = make([]byte, 16)
	for {
		read, err := io.ReadFull(c.conn, buffer)
		if err != nil {
			// TODO: Post to error channel
			fmt.Println("error reading socket")
			continue
		}

		if read != len(buffer) {
			fmt.Println("nothing to read")
			continue
		}

		header, err := parseHeader(bytes.NewBuffer(buffer))

		payload := make([]byte, header.length)
		c.conn.Read(payload)
		value, _ := DeserializeXmmsValue(bytes.NewBuffer(payload))
		c.inbound <- reply{header.sequenceNr, value}
	}
}

func (c *Client) writer() {
	for msg := range c.outbound {
		var payload bytes.Buffer

		c.registry <- msg.context

		err := SerializeXmmsValue(msg.args, &payload)
		if err != nil {
			continue
		}

		header := header{
			objectId:   msg.objectId,
			commandId:  msg.commandId,
			sequenceNr: msg.context.sequenceNr,
			length:     uint32(len(payload.Bytes())),
		}

		err = writeHeader(c.conn, &header)
		if err != nil {
			continue
		}

		payload.WriteTo(c.conn)
		if err != nil {
			continue
		}
	}
}

func (c *Client) router() {
	var registry = make(map[uint32](context))

	for {
		select {
		case ctx := <-c.registry:
			registry[ctx.sequenceNr] = ctx
		case reply := <-c.inbound:
			ctx := registry[reply.sequenceNr]
			ctx.result <- reply.value
			if !ctx.broadcast {
				delete(registry, ctx.sequenceNr)
			}
		}
	}
}

// TODO: Needs a better home, should be generated.
func (c *Client) MainHello(client_name string) XmmsValue {
	context := context{
		result:     make(chan XmmsValue),
		sequenceNr: c.nextSequenceNr()}
	c.outbound <- message{
		objectId:  ObjectMain,
		commandId: CommandMainHello,
		context:   context,
		args:      NewXmmsList(XmmsInt(IpcVersion), XmmsString(client_name))}
	return <-context.result
}

// TODO: Needs a better home, should be generated.
func (c *Client) MainListPlugins() XmmsValue {
	context := context{
		result:     make(chan XmmsValue),
		sequenceNr: c.nextSequenceNr()}
	c.outbound <- message{
		args:      NewXmmsList(XmmsInt(0)),
		objectId:  ObjectMain,
		commandId: CommandMainListPlugins,
		context:   context}
	return <-context.result
}

// TODO: Probably something else that creates a new Client rather than Dial.
func Dial(url string, name string) (*Client, error) {
	addr, err := net.ResolveTCPAddr("tcp", url)
	if err != nil {
		return nil, err
	}

	conn, err := net.DialTCP("tcp", nil, addr)
	if err != nil {
		return nil, err
	}

	client := Client{
		conn:     conn,
		inbound:  make(chan reply),
		outbound: make(chan message),
		registry: make(chan context),
	}

	go client.router()
	go client.reader()
	go client.writer()

	client.clientId = client.MainHello(name)

	return &client, nil
}
<|endoftext|>


<commit_before>package jira

import (
	"fmt"
)

// RoleService handles roles for the JIRA instance / API.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/3/role
type RoleService struct {
	client *Client
}

// Role represents a JIRA product role
type Role struct {
	Self        string   `json:"self" structs:"self"`
	Name        string   `json:"name" structs:"name"`
	ID          int      `json:"id" structs:"id"`
	Description string   `json:"description" structs:"description"`
	Actors      []*Actor `json:"actors" structs:"actors"`
}

// Actor represents a JIRA user
type Actor struct {
	ID          int        `json:"id" structs:"id"`
	DisplayName string     `json:"displayName" structs:"displayName"`
	Type        string     `json:"type" structs:"type"`
	Name        string     `json:"name" structs:"name"`
	AvatarURL   string     `json:"avatarUrl" structs:"avatarUrl"`
	ActorUser   *ActorUser `json:"actorUser" structs:"actoruser"`
}

// ActorUser contains the account id of the actor/user
type ActorUser struct {
	AccountID string `json:"accountId" structs:"accountId"`
}

// GetList returns a list of all available project roles
func (s *RoleService) GetList() (*[]Role, *Response, error) {
	apiEndpoint := "rest/api/3/role"
	req, err := s.client.NewRequest("GET", apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}
	roles := new([]Role)
	resp, err := s.client.Do(req, roles)
	if err != nil {
		jerr := NewJiraError(resp, err)
		return nil, resp, jerr
	}
	return roles, resp, err
}

// Get retreives a single Role from Jira
func (s *RoleService) Get(roleID int) (*Role, *Response, error) {
	apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID)
	req, err := s.client.NewRequest("GET", apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}
	role := new(Role)
	resp, err := s.client.Do(req, role)
	if err != nil {
		jerr := NewJiraError(resp, err)
		return nil, resp, jerr
	}

	return role, resp, err
}
<commit_msg>Fix of documentation urls<commit_after>package jira

import (
	"fmt"
)

// RoleService handles roles for the JIRA instance / API.
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Role
type RoleService struct {
	client *Client
}

// Role represents a JIRA product role
type Role struct {
	Self        string   `json:"self" structs:"self"`
	Name        string   `json:"name" structs:"name"`
	ID          int      `json:"id" structs:"id"`
	Description string   `json:"description" structs:"description"`
	Actors      []*Actor `json:"actors" structs:"actors"`
}

// Actor represents a JIRA actor
type Actor struct {
	ID          int        `json:"id" structs:"id"`
	DisplayName string     `json:"displayName" structs:"displayName"`
	Type        string     `json:"type" structs:"type"`
	Name        string     `json:"name" structs:"name"`
	AvatarURL   string     `json:"avatarUrl" structs:"avatarUrl"`
	ActorUser   *ActorUser `json:"actorUser" structs:"actoruser"`
}

// ActorUser contains the account id of the actor/user
type ActorUser struct {
	AccountID string `json:"accountId" structs:"accountId"`
}

// GetList returns a list of all available project roles
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get
func (s *RoleService) GetList() (*[]Role, *Response, error) {
	apiEndpoint := "rest/api/3/role"
	req, err := s.client.NewRequest("GET", apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}
	roles := new([]Role)
	resp, err := s.client.Do(req, roles)
	if err != nil {
		jerr := NewJiraError(resp, err)
		return nil, resp, jerr
	}
	return roles, resp, err
}

// Get retreives a single Role from Jira
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get
func (s *RoleService) Get(roleID int) (*Role, *Response, error) {
	apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID)
	req, err := s.client.NewRequest("GET", apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}
	role := new(Role)
	resp, err := s.client.Do(req, role)
	if err != nil {
		jerr := NewJiraError(resp, err)
		return nil, resp, jerr
	}

	return role, resp, err
}
<|endoftext|>


<commit_before>package mysql

import (
  "fmt"
  "strings"
  "os"
  _ "github.com/go-sql-driver/mysql"
  "database/sql"
  "log"
)

/* 起動前に変更 */
const User = "root"
const Pass = ""
const Port = "3306"

type MySQL struct {
  db *sql.DB
  dbName string
}

type ColumnDefinition struct {
  name string
  dataType string
  primaryKey bool
}

type Column struct {
  name string
  value string
}

func NewColumnDefinition(n string, dt string, pk bool) *ColumnDefinition {
  columnDef := &ColumnDefinition{name: n, dataType: dt, primaryKey: pk}
  return columnDef
}

func NewColumn(n string, v string) *Column {
  column := &Column{name: n, value: v}
  return column
}

func (this *MySQL) ConnectServer() {
  var err error
  oSql := User + ":" + Pass + "@tcp(" + os.Getenv("TEST_DB_PORT_3306_TCP_ADDR") + ":" + Port + ")/"
  fmt.Println("ConnectServer:" + oSql)
  this.db, err = sql.Open("mysql", oSql)
  if err != nil {
    log.Fatal(err)
  }
}

func (this *MySQL) UseDB(dbName string) {
  uSql := "USE " + dbName
  fmt.Println("UseDB:" + uSql)
  var err error
  _, err = this.db.Exec(uSql)
  if err != nil {
    fmt.Println(err)
  } else {
    this.dbName = dbName
  }
}

func (this *MySQL) CreateDBSetChar(dbName string, charType string) {
  createDBSql := "CREATE DATABASE " + dbName
  if len(charType) != 0 {
    createDBSql += " CHARACTER SET " + charType
  }
  fmt.Println("CreateDBSetChar:" + createDBSql)
  var err error
  _, err = this.db.Exec(createDBSql)
  if err != nil {
    fmt.Println(err)
  }
  this.UseDB(dbName)
}

func (this *MySQL) CreateDB(dbName string) {
  this.CreateDBSetChar(dbName, "")
}

func (this *MySQL) CreateTable(tableName string, columns []ColumnDefinition) {
  cSql := "("
  var pkeys []string
  for _, column := range columns {
    cSql += column.name + " " + column.dataType
    if column.primaryKey {
      cSql += " NOT NULL"
      pkeys = append(pkeys, column.name)
    }
    cSql += ","
  }
  cSql = strings.Trim(cSql, ",")
  cSql += ")"
  cSql = "CREATE TABLE " + tableName + cSql
  fmt.Println("CreateTable:" + cSql)
  var cterr error
  _, cterr = this.db.Exec(cSql)
  if cterr != nil {
    fmt.Println(cterr)
  } else if len(pkeys) != 0 {
    pSql := "ALTER TABLE " + tableName + " ADD PRIMARY KEY("
    for _, key := range pkeys {
      pSql += key + ","
    }
    pSql = strings.Trim(pSql, ",")
    pSql += ")"
    fmt.Println("CreateTable: " + pSql)
    _, perr := this.db.Exec(pSql)
    if perr != nil {
      fmt.Println(perr)
    }
  }
}

func (this *MySQL) Close() {
  var err error
  err = this.db.Close()
  if err != nil {
    log.Fatal(err)
  }
}

func (this *MySQL) Insert (tableName string, columns []Column) {
  iSql := "INSERT INTO " + tableName + " "

  names := "("
  for _, column := range columns {
    names += column.name + ","
  }
  names = strings.Trim(names, ",")
  names += ")"

  values := " VALUES ("
  for _, column := range columns {
    values += column.value + ","
  }
  values = strings.Trim(values, ",")
  values += ")"

  iSql += names + values

  fmt.Println("Insert:" + iSql)
  var err error
  _, err = this.db.Exec(iSql)
  if err != nil {
      fmt.Print("insert error: ")
      fmt.Println(err)
  }
}

func (this *MySQL) Update(tableName string, setColumn string, setValue string, whereColumn string, whereValue string) {
  uSql := "UPDATE " + tableName + " SET " + setColumn + " = " + setValue + " WHERE " + whereColumn + " = " + whereValue
  fmt.Println("Update:" + uSql)
  var err error
  _, err = this.db.Exec(uSql)
  if err != nil {
    fmt.Print("update error: ")
    fmt.Println(err)
  }
}

func (this *MySQL) Delete(tableName string, whereColumn string, whereValue string) {
  dSql := "DELETE FROM " + tableName + " WHERE " + whereColumn + " = " + whereValue
  fmt.Println("Delete:" + dSql)
  var err error
  _, err = this.db.Exec(dSql)
  if err != nil {
    fmt.Print("delete error: ")
    fmt.Println(err)
  }
}

func (this *MySQL) Query(qSql string) *sql.Rows {
  rows, err := this.db.Query(qSql)
  if err != nil {
    fmt.Print("query error: ")
    fmt.Println(err)
  }
  return rows;
}

func (this *MySQL) QueryRow(qSql string) *sql.Row {
  row := this.db.QueryRow(qSql)
  return row;
}

func (this *MySQL) FetchAll(qSql string) [][]interface{} {
  rows, err := this.db.Query(qSql)
  if err != nil {
    fmt.Print("fetch all error: ")
    fmt.Println(err)
  }
  defer rows.Close()

  columns, _ := rows.Columns()
  count := len(columns)
  valuePtrs := make([]interface{}, count)

  ret := make([][]interface{}, 0)
  for rows.Next() {

    values := make([]interface{}, count)
    for i, _ := range columns {
      valuePtrs[i] = &values[i]
    }
    rows.Scan(valuePtrs...)

    for i, _ := range columns {
      var v interface{}
      val := values[i]
      b, ok := val.([]byte)
      if (ok) {
        v = string(b)
      } else {
        v = val
      }
      values[i] = v
    }
    ret = append(ret, values)
  }

  return ret;
}
<commit_msg>Update mysql.go<commit_after>package mysql

import (
  "fmt"
  "strings"
  "os"
  _ "github.com/go-sql-driver/mysql"
  "database/sql"
  "log"
)

/* 起動前に変更 */
const User = "root"
const Pass = ""
const Port = "3306"

type MySQL struct {
  db *sql.DB
  dbName string
}

type ColumnDefinition struct {
  name string
  dataType string
  primaryKey bool
}

type Column struct {
  name string
  value string
}

func NewColumnDefinition(n string, dt string, pk bool) *ColumnDefinition {
  columnDef := &ColumnDefinition{name: n, dataType: dt, primaryKey: pk}
  return columnDef
}

func NewColumn(n string, v string) *Column {
  column := &Column{name: n, value: v}
  return column
}

func (this *MySQL) ConnectServer() {
  var err error
  oSql := User + ":" + Pass + "@tcp(" + os.Getenv("TEST_DB_PORT_3306_TCP_ADDR") + ":" + Port + ")/"
  fmt.Println("ConnectServer:" + oSql)
  this.db, err = sql.Open("mysql", oSql)
  if err != nil {
    log.Fatal(err)
  }
}

func (this *MySQL) UseDB(dbName string) {
  uSql := "USE " + dbName
  fmt.Println("UseDB:" + uSql)
  var err error
  _, err = this.db.Exec(uSql)
  if err != nil {
    fmt.Println(err)
  } else {
    this.dbName = dbName
  }
}

func (this *MySQL) CreateDBSetChar(dbName string, charType string) {
  createDBSql := "CREATE DATABASE " + dbName
  if len(charType) != 0 {
    createDBSql += " CHARACTER SET " + charType
  }
  fmt.Println("CreateDBSetChar:" + createDBSql)
  var err error
  _, err = this.db.Exec(createDBSql)
  if err != nil {
    fmt.Println(err)
  }
  this.UseDB(dbName)
}

func (this *MySQL) CreateDB(dbName string) {
  this.CreateDBSetChar(dbName, "")
}

func (this *MySQL) CreateTable(tableName string, columns []ColumnDefinition) {
  cSql := "("
  var pkeys []string
  for _, column := range columns {
    cSql += column.name + " " + column.dataType
    if column.primaryKey {
      cSql += " NOT NULL"
      pkeys = append(pkeys, column.name)
    }
    cSql += ","
  }
  cSql = strings.Trim(cSql, ",")
  cSql += ")"
  cSql = "CREATE TABLE " + tableName + cSql
  fmt.Println("CreateTable:" + cSql)
  var cterr error
  _, cterr = this.db.Exec(cSql)
  if cterr != nil {
    fmt.Println(cterr)
  } else if len(pkeys) != 0 {
    pSql := "ALTER TABLE " + tableName + " ADD PRIMARY KEY("
    for _, key := range pkeys {
      pSql += key + ","
    }
    pSql = strings.Trim(pSql, ",")
    pSql += ")"
    fmt.Println("CreateTable: " + pSql)
    _, perr := this.db.Exec(pSql)
    if perr != nil {
      fmt.Println(perr)
    }
  }
}

func (this *MySQL) Close() {
  var err error
  err = this.db.Close()
  if err != nil {
    log.Fatal(err)
  }
}

func (this *MySQL) Insert (tableName string, columns []Column) {
  iSql := "INSERT INTO " + tableName + " "

  names := "("
  for _, column := range columns {
    names += column.name + ","
  }
  names = strings.Trim(names, ",")
  names += ")"

  values := " VALUES ("
  for _, column := range columns {
    values += column.value + ","
  }
  values = strings.Trim(values, ",")
  values += ")"

  iSql += names + values

  // fmt.Println("Insert:" + iSql)
  var err error
  _, err = this.db.Exec(iSql)
  if err != nil {
      fmt.Print("insert error: ")
      fmt.Println(err)
  }
}

func (this *MySQL) Update(tableName string, setColumn string, setValue string, whereColumn string, whereValue string) {
  uSql := "UPDATE " + tableName + " SET " + setColumn + " = " + setValue + " WHERE " + whereColumn + " = " + whereValue
  fmt.Println("Update:" + uSql)
  var err error
  _, err = this.db.Exec(uSql)
  if err != nil {
    fmt.Print("update error: ")
    fmt.Println(err)
  }
}

func (this *MySQL) Delete(tableName string, whereColumn string, whereValue string) {
  dSql := "DELETE FROM " + tableName + " WHERE " + whereColumn + " = " + whereValue
  fmt.Println("Delete:" + dSql)
  var err error
  _, err = this.db.Exec(dSql)
  if err != nil {
    fmt.Print("delete error: ")
    fmt.Println(err)
  }
}

func (this *MySQL) Query(qSql string) *sql.Rows {
  rows, err := this.db.Query(qSql)
  if err != nil {
    fmt.Print("query error: ")
    fmt.Println(err)
  }
  return rows;
}

func (this *MySQL) QueryRow(qSql string) *sql.Row {
  row := this.db.QueryRow(qSql)
  return row;
}

func (this *MySQL) FetchAll(qSql string) [][]interface{} {
  rows, err := this.db.Query(qSql)
  if err != nil {
    fmt.Print("fetch all error: ")
    fmt.Println(err)
  }
  defer rows.Close()

  columns, _ := rows.Columns()
  count := len(columns)
  valuePtrs := make([]interface{}, count)

  ret := make([][]interface{}, 0)
  for rows.Next() {

    values := make([]interface{}, count)
    for i, _ := range columns {
      valuePtrs[i] = &values[i]
    }
    rows.Scan(valuePtrs...)

    for i, _ := range columns {
      var v interface{}
      val := values[i]
      b, ok := val.([]byte)
      if (ok) {
        v = string(b)
      } else {
        v = val
      }
      values[i] = v
    }
    ret = append(ret, values)
  }

  return ret;
}
<|endoftext|>