func (s *State) String() string {
	if s == nil {
		return "<nil>"
	}
	modules := make([]string, 0, len(s.Modules))
	for m := range s.Modules {
		modules = append(modules, m)
	}
	sort.Strings(modules)
	var buf bytes.Buffer
	for _, name := range modules {
		m := s.Modules[name]
		mStr := m.testString()
		if m.Addr.IsRoot() {
			buf.WriteString(mStr + "\n")
			continue
		}
		buf.WriteString("module")
		for _, step := range m.Addr {
			buf.WriteByte('.')
			buf.WriteString(step.Name)
			if step.InstanceKey != addrs.NoKey {
				buf.WriteByte('[')
				buf.WriteString(step.InstanceKey.String())
				buf.WriteByte(']')
			}
		}
		buf.WriteString(":\n")
		s := bufio.NewScanner(strings.NewReader(mStr))
		for s.Scan() {
			text := s.Text()
			if text != "" {
				text = "  " + text
			}
			buf.WriteString(fmt.Sprintf("%s\n", text))
		}
	}
	return strings.TrimSpace(buf.String())
}
String returns a rather-odd string representation of the entire state.

This is intended to match the behavior of the older terraform.State.String
method that is used in lots of existing tests. It should not be used in
new tests: instead, use "cmp" to directly compare the state data structures
and print out a diff if they do not match.

This method should never be used in non-test code, whether directly by call
or indirectly via a %s or %q verb in package fmt.
package states
import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	ctyjson "github.com/zclconf/go-cty/cty/json"
	"github.com/hashicorp/terraform/addrs"
	"github.com/hashicorp/terraform/configs/hcl2shim"
)