Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Provider-Defined Function Testing #202

Closed
bflad opened this issue Dec 18, 2023 · 2 comments · Fixed by #210
Closed

Add Provider-Defined Function Testing #202

bflad opened this issue Dec 18, 2023 · 2 comments · Fixed by #210

Comments

@bflad
Copy link
Contributor

bflad commented Dec 18, 2023

Description

Once terraform-plugin-framework v1.5.0 is released with provider-defined function support and the dependency updated in this repository, it will be great to add "smoke" testing for functions. My suggestions for what would be useful to be added:

  • In the framework providers, testing for each parameter type and return type (my suggestion would be something like an "echo" function for each)
  • In the framework providers, testing for variadic parameters
  • In the go providers, single function test of any form is fine
  • In the mux providers, single function test of any form is fine

Anything beyond this is a bonus. 👍

Here's a quick example I was using when I was double checking the prototype code of everything.

internal/framework6provider/string_function.go

package framework

import (
	"context"

	"github.com/hashicorp/terraform-plugin-framework/function"
)

var _ function.Function = StringFunction{}

func NewStringFunction() function.Function {
	return &StringFunction{}
}

type StringFunction struct{}

func (f StringFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) {
	resp.Name = "string"
}

func (f StringFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) {
	resp.Definition = function.Definition{
		Parameters: []function.Parameter{
			function.StringParameter{},
		},
		Return: function.StringReturn{},
	}
}

func (f StringFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) {
	var arg string

	resp.Diagnostics.Append(req.Arguments.Get(ctx, &arg)...)

	resp.Diagnostics.Append(resp.Result.Set(ctx, arg)...)
}

internal/framework6provider/string_function_test.go (needs TerraformVersionChecks for 1.8+ 😄 )

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package framework

import (
	"regexp"
	"testing"

	"github.com/hashicorp/terraform-plugin-framework/providerserver"
	"github.com/hashicorp/terraform-plugin-go/tfprotov6"
	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
	"github.com/hashicorp/terraform-plugin-testing/plancheck"
)

func TestStringFunction_known(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
			"framework": providerserver.NewProtocol6WithError(New()),
		},
		Steps: []resource.TestStep{
			{
				Config: `
				output "test" {
					value = provider::framework::string("test-value")
				}`,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckOutput("test", "test-value"),
				),
			},
		},
	})
}

func TestStringFunction_null(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
			"framework": providerserver.NewProtocol6WithError(New()),
		},
		Steps: []resource.TestStep{
			{
				Config: `
				output "test" {
					value = provider::framework::string(null)
				}`,
				ExpectError: regexp.MustCompile("Invalid function argument"),
			},
		},
	})
}

func TestStringFunction_unknown(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
			"framework": providerserver.NewProtocol6WithError(New()),
		},
		Steps: []resource.TestStep{
			{
				Config: `
				resource "terraform_data" "test" {
					input = provider::framework::string("test-value")
				}

				output "test" {
					value = terraform_data.test.output
				}`,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckOutput("test", "test-value"),
				),
				ConfigPlanChecks: resource.ConfigPlanChecks{
					PreApply: []plancheck.PlanCheck{
						ExpectOutputUnknownValue("test"),
					},
				},
			},
		},
	})
}

Apparently I also had a variadic function for prototype checking too (which acted as another "echo" like function):

internal/framework6provider/variadic_function.go

package framework

import (
	"context"

	"github.com/hashicorp/terraform-plugin-framework/function"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

var _ function.Function = VariadicFunction{}

func NewVariadicFunction() function.Function {
	return &VariadicFunction{}
}

type VariadicFunction struct{}

func (f VariadicFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) {
	resp.Name = "variadic"
}

func (f VariadicFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) {
	resp.Definition = function.Definition{
		Return: function.ListReturn{
			ElementType: types.StringType,
		},
		VariadicParameter: function.StringParameter{},
	}
}

func (f VariadicFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) {
	var varg []string

	resp.Diagnostics.Append(req.Arguments.Get(ctx, &varg)...)

	resp.Diagnostics.Append(resp.Result.Set(ctx, varg)...)
}

And internal/framework6provider/variadic_function_test.go (similar missing TerraformVersionChecks 😄 )

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package framework

import (
	"os"
	"regexp"
	"testing"

	"github.com/hashicorp/terraform-plugin-framework/providerserver"
	"github.com/hashicorp/terraform-plugin-go/tfprotov6"
	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
	"github.com/hashicorp/terraform-plugin-testing/plancheck"
)

func TestVariadicFunction_value_zero(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
			"framework": providerserver.NewProtocol6WithError(New()),
		},
		Steps: []resource.TestStep{
			{
				Config: `
				output "test" {
					value = provider::framework::variadic()
				}`,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckOutput("test", "TBD"),
				),
			},
		},
	})
}

func TestVariadicFunction_value_one(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
			"framework": providerserver.NewProtocol6WithError(New()),
		},
		Steps: []resource.TestStep{
			{
				Config: `
				output "test" {
					value = provider::framework::variadic("one")
				}`,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckOutput("test", "TBD"),
				),
			},
		},
	})
}

func TestVariadicFunction_value_multiple(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
			"framework": providerserver.NewProtocol6WithError(New()),
		},
		Steps: []resource.TestStep{
			{
				Config: `
				output "test" {
					value = provider::framework::variadic("one", "two")
				}`,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckOutput("test", "TBD"),
				),
			},
		},
	})
}

func TestVariadicFunction_null(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
			"framework": providerserver.NewProtocol6WithError(New()),
		},
		Steps: []resource.TestStep{
			{
				Config: `
				output "test" {
					value = provider::framework::variadic(null)
				}`,
				ExpectError: regexp.MustCompile("Invalid function argument"),
			},
		},
	})
}

func TestVariadicFunction_unknown(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
			"framework": providerserver.NewProtocol6WithError(New()),
		},
		Steps: []resource.TestStep{
			{
				Config: `
				resource "terraform_data" "test" {
					input = provider::framework::variadic("test-value")
				}

				output "test" {
					value = terraform_data.test.output
				}`,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckOutput("test", "TBD"),
				),
				ConfigPlanChecks: resource.ConfigPlanChecks{
					PreApply: []plancheck.PlanCheck{
						ExpectOutputUnknownValue("test"),
					},
				},
			},
		},
	})
}
@bflad
Copy link
Contributor Author

bflad commented Jan 11, 2024

terraform-plugin-framework v1.5.0 was released today so this will be ready to pick up very shortly after I poke dependabot.

bendbennett added a commit that referenced this issue Jan 30, 2024
bendbennett added a commit that referenced this issue Jan 30, 2024
bendbennett added a commit that referenced this issue Feb 1, 2024
bendbennett added a commit that referenced this issue Feb 1, 2024
bendbennett added a commit that referenced this issue Mar 6, 2024
* Adding function tests for framework protocol 5 & 6 and protocol 6 mux providers (#202)

* Adding copyright headers (#202)

* Linting (#202)

* Adding function tests for protocol v5 and protocol v6 providers (#202)

* Amending provider defined function tests for number until bug fix on go.cty is released (#202)

* Adding function testing for mux providers (#202)

* Adding function testing for tf6to5provider (#202)

* Adding copyright headers (#202)

* Bumping terraform-plugin-framework version to latest (#202)

Latest terraform-plugin-framework contains fix for:
  * hashicorp/terraform-plugin-framework#914
  * hashicorp/terraform-plugin-framework#919

* Bumping terraform-plugin-testing to v1.7.0 (#202)

* Refactoring to return function.FuncError (#202)

* Updates following code review (#202)
Copy link

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant