You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by ri...@apache.org on 2023/07/10 21:10:49 UTC

[beam] branch master updated: Implement environment variable type (#27407)

This is an automated email from the ASF dual-hosted git repository.

riteshghorse pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new c5d6183cee1 Implement environment variable type (#27407)
c5d6183cee1 is described below

commit c5d6183cee1c22c572cbf51ec4a2c1490dafafe8
Author: Damon <da...@users.noreply.github.com>
AuthorDate: Mon Jul 10 14:10:43 2023 -0700

    Implement environment variable type (#27407)
---
 .test-infra/pipelines/go.mod                       |  21 ++
 .test-infra/pipelines/go.sum                       |   2 +
 .../src/main/go/internal/environment/variable.go   |  81 +++++
 .../main/go/internal/environment/variable_test.go  | 358 +++++++++++++++++++++
 4 files changed, 462 insertions(+)

diff --git a/.test-infra/pipelines/go.mod b/.test-infra/pipelines/go.mod
new file mode 100644
index 00000000000..0204d037a4c
--- /dev/null
+++ b/.test-infra/pipelines/go.mod
@@ -0,0 +1,21 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This module contains all Go code for internal use by .test-infra pipelines.
+module github.com/apache/beam/test-infra/pipelines
+
+go 1.20
+
+require github.com/google/go-cmp v0.5.9 // indirect
diff --git a/.test-infra/pipelines/go.sum b/.test-infra/pipelines/go.sum
new file mode 100644
index 00000000000..62841cdb151
--- /dev/null
+++ b/.test-infra/pipelines/go.sum
@@ -0,0 +1,2 @@
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
diff --git a/.test-infra/pipelines/src/main/go/internal/environment/variable.go b/.test-infra/pipelines/src/main/go/internal/environment/variable.go
new file mode 100644
index 00000000000..1e81a2e2ce8
--- /dev/null
+++ b/.test-infra/pipelines/src/main/go/internal/environment/variable.go
@@ -0,0 +1,81 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package environment provides helpers for interacting with environment variables.
+package environment
+
+import (
+	"fmt"
+	"os"
+	"strings"
+)
+
+// Variable defines an environment variable via a string type alias.
+// Variable's string defaultValue assigns the system environment variable key.
+type Variable string
+
+// Default a defaultValue to the system environment.
+func (v Variable) Default(value string) error {
+	if v.Missing() {
+		return os.Setenv((string)(v), value)
+	}
+	return nil
+}
+
+// Missing reports whether the system environment variable is an empty string.
+func (v Variable) Missing() bool {
+	return v.Value() == ""
+}
+
+// Key returns the system environment variable key.
+func (v Variable) Key() string {
+	return (string)(v)
+}
+
+// Value returns the system environment variable defaultValue.
+func (v Variable) Value() string {
+	return os.Getenv((string)(v))
+}
+
+// KeyValue returns a concatenated string of the system environment variable's
+// <key>=<defaultValue>.
+func (v Variable) KeyValue() string {
+	return fmt.Sprintf("%s=%s", (string)(v), v.Value())
+}
+
+// Missing reports as an error listing all Variable among vars that are
+// not assigned in the system environment.
+func Missing(vars ...Variable) error {
+	var missing []string
+	for _, v := range vars {
+		if v.Missing() {
+			missing = append(missing, v.KeyValue())
+		}
+	}
+	if len(missing) > 0 {
+		return fmt.Errorf("variables empty but expected from environment: %s", strings.Join(missing, "; "))
+	}
+	return nil
+}
+
+// Map converts a slice of Variable into a map.
+// Its usage is for logging purposes.
+func Map(vars ...Variable) map[string]interface{} {
+	result := map[string]interface{}{}
+	for _, v := range vars {
+		result[(string)(v)] = v.Value()
+	}
+	return result
+}
diff --git a/.test-infra/pipelines/src/main/go/internal/environment/variable_test.go b/.test-infra/pipelines/src/main/go/internal/environment/variable_test.go
new file mode 100644
index 00000000000..6675b39ab26
--- /dev/null
+++ b/.test-infra/pipelines/src/main/go/internal/environment/variable_test.go
@@ -0,0 +1,358 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package environment
+
+import (
+	"errors"
+	"github.com/google/go-cmp/cmp"
+	"os"
+	"testing"
+)
+
+func TestMap(t *testing.T) {
+	type args struct {
+		vars   []Variable
+		values []string
+	}
+	tests := []struct {
+		name string
+		args args
+		want map[string]interface{}
+	}{
+		{
+			name: "{}",
+			args: args{},
+			want: map[string]interface{}{},
+		},
+		{
+			name: "{A=1; B=2; C=3}",
+			args: args{
+				vars: []Variable{
+					"A",
+					"B",
+					"C",
+				},
+				values: []string{
+					"1",
+					"2",
+					"3",
+				},
+			},
+			want: map[string]interface{}{
+				"A": "1",
+				"B": "2",
+				"C": "3",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			clear(tt.args.vars...)
+			set(t, tt.args.vars, tt.args.values)
+			got := Map(tt.args.vars...)
+			if diff := cmp.Diff(got, tt.want); diff != "" {
+				t.Errorf("Map() = %v, want %v, diff:\n%v", got, tt.want, diff)
+			}
+		})
+	}
+}
+
+func TestMissing(t *testing.T) {
+	type args struct {
+		vars   []Variable
+		values []string
+	}
+	tests := []struct {
+		name string
+		args args
+		want error
+	}{
+		{
+			name: "{}",
+			args: args{},
+		},
+		{
+			name: "{A=}",
+			args: args{
+				vars: []Variable{
+					"A",
+				},
+				values: []string{
+					"",
+				},
+			},
+			want: errors.New("variables empty but expected from environment: A="),
+		},
+		{
+			name: "{A=1}",
+			args: args{
+				vars: []Variable{
+					"A",
+				},
+				values: []string{
+					"1",
+				},
+			},
+			want: nil,
+		},
+		{
+			name: "{A=; B=}",
+			args: args{
+				vars: []Variable{
+					"A",
+					"B",
+				},
+				values: []string{
+					"",
+					"",
+				},
+			},
+			want: errors.New("variables empty but expected from environment: A=; B="),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var got, want string
+			clear(tt.args.vars...)
+			set(t, tt.args.vars, tt.args.values)
+			err := Missing(tt.args.vars...)
+			if err != nil {
+				got = err.Error()
+			}
+			if tt.want != nil {
+				want = tt.want.Error()
+			}
+			if diff := cmp.Diff(got, want); diff != "" {
+				t.Errorf("Missing() error = %v, want %v, diff:\n%s", err, tt.want, diff)
+			}
+		})
+	}
+}
+
+func TestVariable_Default(t *testing.T) {
+	type args struct {
+		setValue     string
+		defaultValue string
+	}
+	tests := []struct {
+		name string
+		v    Variable
+		args args
+		want string
+	}{
+		{
+			name: "environment variable not set",
+			v:    "A",
+			args: args{
+				defaultValue: "1",
+			},
+			want: "1",
+		},
+		{
+			name: "environment variable default is overridden by set value",
+			v:    "A",
+			args: args{
+				setValue:     "2",
+				defaultValue: "1",
+			},
+			want: "2",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			clear(tt.v)
+			if tt.args.setValue != "" {
+				set(t, []Variable{tt.v}, []string{tt.args.setValue})
+			}
+			if err := tt.v.Default(tt.args.defaultValue); err != nil {
+				t.Fatalf("could not set default environment variable value during test execution: %v", err)
+			}
+			got := os.Getenv(tt.v.Key())
+			if diff := cmp.Diff(got, tt.want); diff != "" {
+				t.Errorf("Default() = %s, want %s, diff:\n%s", got, tt.want, diff)
+			}
+		})
+	}
+}
+
+func TestVariable_KeyValue(t *testing.T) {
+	tests := []struct {
+		name  string
+		v     Variable
+		value string
+		want  string
+	}{
+		{
+			name: "environment variable not set",
+			v:    "A",
+			want: "A=",
+		},
+		{
+			name:  "environment variable is set",
+			v:     "A",
+			value: "1",
+			want:  "A=1",
+		},
+	}
+	for _, tt := range tests {
+		clear(tt.v)
+		t.Run(tt.name, func(t *testing.T) {
+			set(t, []Variable{tt.v}, []string{tt.value})
+			got := tt.v.KeyValue()
+			if diff := cmp.Diff(got, tt.want); diff != "" {
+				t.Errorf("KeyValue() = %v, want %v, diff:\n%s", got, tt.want, diff)
+			}
+		})
+	}
+}
+
+func TestVariable_Missing(t *testing.T) {
+	type args struct {
+		setValue     string
+		defaultValue string
+	}
+	tests := []struct {
+		name string
+		args args
+		v    Variable
+		want bool
+	}{
+		{
+			name: "no default and not set",
+			args: args{},
+			v:    "A",
+			want: true,
+		},
+		{
+			name: "has default but not set",
+			args: args{
+				defaultValue: "1",
+			},
+			v:    "A",
+			want: false,
+		},
+		{
+			name: "no default but set",
+			args: args{
+				setValue: "1",
+			},
+			v:    "A",
+			want: false,
+		},
+		{
+			name: "has default and set",
+			args: args{
+				setValue:     "2",
+				defaultValue: "1",
+			},
+			v:    "A",
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			clear(tt.v)
+			if tt.args.defaultValue != "" {
+				if err := tt.v.Default(tt.args.defaultValue); err != nil {
+					t.Fatalf("could not set default environment variable value during test execution: %v", err)
+				}
+			}
+			if tt.args.setValue != "" {
+				set(t, []Variable{tt.v}, []string{tt.args.setValue})
+			}
+			if got := tt.v.Missing(); got != tt.want {
+				t.Errorf("Missing() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestVariable_Value(t *testing.T) {
+	type args struct {
+		setValue     string
+		defaultValue string
+	}
+	tests := []struct {
+		name string
+		args args
+		v    Variable
+		want string
+	}{
+		{
+			name: "no default and not set",
+			args: args{},
+			v:    "A",
+			want: "",
+		},
+		{
+			name: "has default but not set",
+			args: args{
+				defaultValue: "1",
+			},
+			v:    "A",
+			want: "1",
+		},
+		{
+			name: "no default but set",
+			args: args{
+				setValue: "1",
+			},
+			v:    "A",
+			want: "1",
+		},
+		{
+			name: "has default and set",
+			args: args{
+				setValue:     "2",
+				defaultValue: "1",
+			},
+			v:    "A",
+			want: "2",
+		},
+	}
+	for _, tt := range tests {
+		clear(tt.v)
+		if tt.args.defaultValue != "" {
+			if err := tt.v.Default(tt.args.defaultValue); err != nil {
+				t.Fatalf("could not set default environment variable value during test execution: %v", err)
+			}
+		}
+		if tt.args.setValue != "" {
+			set(t, []Variable{tt.v}, []string{tt.args.setValue})
+		}
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.v.Value(); got != tt.want {
+				t.Errorf("Value() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func clear(vars ...Variable) {
+	for _, k := range vars {
+		_ = os.Setenv(k.Key(), "")
+	}
+}
+
+func set(t *testing.T, vars []Variable, values []string) {
+	if len(vars) != len(values) {
+		t.Fatalf("test cases should be configured with matching args.vars and args.values: len(tt.args.vars): %v != len(tt.args.values): %v", len(vars), len(values))
+	}
+	for i := range vars {
+		key := vars[i].Key()
+		value := values[i]
+		_ = os.Setenv(key, value)
+	}
+}