Repository: evcc-io/evcc
Pull request #2759 (2022-03-02 17:44:21)

Test and instantiate all template usages
Fix https://github.com/evcc-io/evcc/issues/2684


Patch:
diff --git a/core/loadpoint.go b/core/loadpoint.go
index a544cd74..ac91119e 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -1285,14 +1285,12 @@ func (lp *LoadPoint) updateChargeCurrents() {
 	lp.publish("chargeCurrents", lp.chargeCurrents)
 
 	if lp.charging() {
-		// Quine-McCluskey for (¬L1∧L2∧¬L3) ∨ (¬L1∧¬L2∧L3) ∨ (L1∧¬L2∧L3) ∨ (¬L1∧L2∧L3) -> ¬L1 ∧ L2 ∨ ¬L2 ∧ L3
-		if !(i1 > minActiveCurrent) && (i2 > minActiveCurrent) || !(i2 > minActiveCurrent) && (i3 > minActiveCurrent) {
-			lp.log.WARN.Printf("invalid phase wiring between charge meter and vehicle")
-		}
-
 		var phases int
-		for _, i := range lp.chargeCurrents {
-			if i > minActiveCurrent {
+		for l, i := range lp.chargeCurrents {
+			if i >= minActiveCurrent {
+				if l > 0 && phases == 0 {
+					lp.log.WARN.Printf("invalid wiring: phase %d observed active should be first phase", l+1)
+				}
 				phases++
 			}
 		}
diff --git a/util/templates/render.go b/util/templates/render.go
new file mode 100644
index 00000000..5aa96f59
--- /dev/null
+++ b/util/templates/render.go
@@ -0,0 +1,43 @@
+package templates
+
+import (
+	"os"
+	"testing"
+
+	"gopkg.in/yaml.v3"
+)
+
+// RenderTest renders and instantiates plus yaml-parses the template per usage
+func RenderTest(t *testing.T, tmpl Template, values map[string]interface{}, cb func(values map[string]interface{})) {
+	t.Run(tmpl.Template, func(t *testing.T) {
+		t.Parallel()
+
+		b, values, err := tmpl.RenderResult(TemplateRenderModeUnitTest, values)
+		if err != nil {
+			t.Log(string(b))
+			t.Error(err)
+		}
+
+		// instantiate all usage variants
+		for _, u := range tmpl.Usages() {
+			t.Run(u, func(t *testing.T) {
+				values[ParamUsage] = u
+
+				b, _, err := tmpl.RenderResult(TemplateRenderModeInstance, values)
+				if err != nil {
+					t.Errorf("usage: %s, result: %v", u, err)
+				}
+
+				var instance interface{}
+				if err := yaml.Unmarshal(b, &instance); err != nil {
+					t.Errorf("usage: %s, yaml: %v", u, err)
+				}
+
+				// actually run the instance if not on CI
+				if os.Getenv("CI") == "" {
+					cb(values)
+				}
+			})
+		}
+	})
+}
diff --git a/util/templates/template.go b/util/templates/template.go
index 266d7689..fee07495 100644
--- a/util/templates/template.go
+++ b/util/templates/template.go
@@ -89,14 +89,7 @@ func (t *Template) SetCombinedTitle() {
 		t.resolveTitles()
 	}
 
-	title := ""
-	for _, t := range t.titles {
-		if title != "" {
-			title += "/"
-		}
-		title += t
-	}
-	t.title = title
+	t.title = strings.Join(t.titles, "/")
 }
 
 // set the title for this templates
@@ -111,21 +104,7 @@ func (t *Template) Title() string {
 
 // return a language specific product title
 func (t *Template) ProductTitle(p Product) string {
-	title := ""
-
-	if p.Brand != "" {
-		title += p.Brand
-	}
-
-	description := p.Description.String(t.Lang)
-	if description != "" {
-		if title != "" {
-			title += " "
-		}
-		title += description
-	}
-
-	return title
+	return strings.TrimSpace(fmt.Sprintf("%s %s", p.Brand, p.Description.String(t.Lang)))
 }
 
 // return the language specific product titles
@@ -141,13 +120,9 @@ func (t *Template) Titles(lang string) []string {
 
 // set the language specific product titles
 func (t *Template) resolveTitles() {
-	var titles []string
-
 	for _, p := range t.Products {
-		titles = append(titles, t.ProductTitle(p))
+		t.titles = append(t.titles, t.ProductTitle(p))
 	}
-
-	t.titles = titles
 }
 
 // add the referenced base Params and overwrite existing ones


Test patch:
diff --git a/charger/template_test.go b/charger/template_test.go
index 70df77c2..b890d09f 100644
--- a/charger/template_test.go
+++ b/charger/template_test.go
@@ -30,7 +30,7 @@ var acceptable = []string{
 	"Get \"http://192.0.2.2/getParameters\": context deadline exceeded", // evsewifi
 }
 
-func TestChargerTemplates(t *testing.T) {
+func TestTemplates(t *testing.T) {
 	test.SkipCI(t)
 
 	for _, tmpl := range templates.ByClass(templates.Charger) {
@@ -54,20 +54,8 @@ func TestChargerTemplates(t *testing.T) {
 			values = tmpl.ModbusValues(templates.TemplateRenderModeInstance, true, values)
 		}
 
-		t.Run(tmpl.Template, func(t *testing.T) {
-			t.Parallel()
-
-			b, values, err := tmpl.RenderResult(templates.TemplateRenderModeUnitTest, values)
-			if err != nil {
-				t.Logf("Template: %s", tmpl.Template)
-				t.Logf("%s", string(b))
-				t.Error(err)
-			}
-
-			_, err = NewFromConfig("template", values)
-			if err != nil && !test.Acceptable(err, acceptable) {
-				t.Logf("Template: %s", tmpl.Template)
-				t.Logf("%s", string(b))
+		templates.RenderTest(t, tmpl, values, func(values map[string]interface{}) {
+			if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
 				t.Error(err)
 			}
 		})
diff --git a/meter/template_test.go b/meter/template_test.go
index d64c3c44..8d1cd48c 100644
--- a/meter/template_test.go
+++ b/meter/template_test.go
@@ -27,9 +27,7 @@ var acceptable = []string{
 	"login failed: Put \"https://192.0.2.2/v1/login\": context deadline exceeded", // LG ESS
 }
 
-func TestMeterTemplates(t *testing.T) {
-	test.SkipCI(t)
-
+func TestTemplates(t *testing.T) {
 	for _, tmpl := range templates.ByClass(templates.Meter) {
 		tmpl := tmpl
 
@@ -51,39 +49,10 @@ func TestMeterTemplates(t *testing.T) {
 			values = tmpl.ModbusValues(templates.TemplateRenderModeInstance, true, values)
 		}
 
-		usages := tmpl.Usages()
-		if len(usages) == 0 {
-			runTest(t, tmpl, values)
-		} else {
-			// test all usages
-			for _, usage := range usages {
-
-				// set the usage param value
-				if usage != "" {
-					values[templates.ParamUsage] = usage
-				}
-
-				runTest(t, tmpl, values)
+		templates.RenderTest(t, tmpl, values, func(values map[string]interface{}) {
+			if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
+				t.Error(err)
 			}
-		}
+		})
 	}
 }
-
-func runTest(t *testing.T, tmpl templates.Template, values map[string]interface{}) {
-	t.Run(tmpl.Template, func(t *testing.T) {
-		t.Parallel()
-
-		b, values, err := tmpl.RenderResult(templates.TemplateRenderModeUnitTest, values)
-		if err != nil {
-			t.Logf("Template: %s", tmpl.Template)
-			t.Logf("%s", string(b))
-			t.Error(err)
-		}
-
-		if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
-			t.Logf("Template: %s", tmpl.Template)
-			t.Logf("%s", string(b))
-			t.Error(err)
-		}
-	})
-}
diff --git a/vehicle/template_test.go b/vehicle/template_test.go
index aaa2d37e..d43b61c6 100644
--- a/vehicle/template_test.go
+++ b/vehicle/template_test.go
@@ -35,9 +35,7 @@ var acceptable = []string{
 	"401: Unauthorized: Invalid credentials", // Volvo
 }
 
-func TestVehicleTemplates(t *testing.T) {
-	test.SkipCI(t)
-
+func TestTemplates(t *testing.T) {
 	for _, tmpl := range templates.ByClass(templates.Vehicle) {
 		tmpl := tmpl
 
@@ -47,20 +45,8 @@ func TestVehicleTemplates(t *testing.T) {
 		// set the template value which is needed for rendering
 		values["template"] = tmpl.Template
 
-		t.Run(tmpl.Template, func(t *testing.T) {
-			t.Parallel()
-
-			b, values, err := tmpl.RenderResult(templates.TemplateRenderModeUnitTest, values)
-			if err != nil {
-				t.Logf("Template: %s", tmpl.Template)
-				t.Logf("%s", string(b))
-				t.Error(err)
-			}
-
-			_, err = NewFromConfig("template", values)
-			if err != nil && !test.Acceptable(err, acceptable) {
-				t.Logf("Template: %s", tmpl.Template)
-				t.Logf("%s", string(b))
+		templates.RenderTest(t, tmpl, values, func(values map[string]interface{}) {
+			if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
 				t.Error(err)
 			}
 		})