| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | // Copyright 2015 The go-ethereum Authors | 
					
						
							|  |  |  | // This file is part of the go-ethereum library. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Lesser General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  | // GNU Lesser General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Lesser General Public License | 
					
						
							|  |  |  | // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package console | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/common" | 
					
						
							| 
									
										
										
										
											2017-11-24 22:10:27 +08:00
										 |  |  | 	"github.com/ethereum/go-ethereum/consensus/ethash" | 
					
						
							| 
									
										
										
										
											2020-05-19 10:44:46 +02:00
										 |  |  | 	"github.com/ethereum/go-ethereum/console/prompt" | 
					
						
							| 
									
										
										
										
											2017-03-02 14:03:33 +01:00
										 |  |  | 	"github.com/ethereum/go-ethereum/core" | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	"github.com/ethereum/go-ethereum/eth" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/internal/jsre" | 
					
						
							| 
									
										
										
										
											2019-04-23 15:08:51 +08:00
										 |  |  | 	"github.com/ethereum/go-ethereum/miner" | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	"github.com/ethereum/go-ethereum/node" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	testInstance = "console-tester" | 
					
						
							|  |  |  | 	testAddress  = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // hookedPrompter implements UserPrompter to simulate use input via channels. | 
					
						
							|  |  |  | type hookedPrompter struct { | 
					
						
							|  |  |  | 	scheduler chan string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *hookedPrompter) PromptInput(prompt string) (string, error) { | 
					
						
							|  |  |  | 	// Send the prompt to the tester | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case p.scheduler <- prompt: | 
					
						
							|  |  |  | 	case <-time.After(time.Second): | 
					
						
							|  |  |  | 		return "", errors.New("prompt timeout") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Retrieve the response and feed to the console | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case input := <-p.scheduler: | 
					
						
							|  |  |  | 		return input, nil | 
					
						
							|  |  |  | 	case <-time.After(time.Second): | 
					
						
							|  |  |  | 		return "", errors.New("input timeout") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *hookedPrompter) PromptPassword(prompt string) (string, error) { | 
					
						
							|  |  |  | 	return "", errors.New("not implemented") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | func (p *hookedPrompter) PromptConfirm(prompt string) (bool, error) { | 
					
						
							|  |  |  | 	return false, errors.New("not implemented") | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-05-19 10:44:46 +02:00
										 |  |  | func (p *hookedPrompter) SetHistory(history []string)                     {} | 
					
						
							|  |  |  | func (p *hookedPrompter) AppendHistory(command string)                    {} | 
					
						
							|  |  |  | func (p *hookedPrompter) ClearHistory()                                   {} | 
					
						
							|  |  |  | func (p *hookedPrompter) SetWordCompleter(completer prompt.WordCompleter) {} | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // tester is a console test environment for the console tests to operate on. | 
					
						
							|  |  |  | type tester struct { | 
					
						
							|  |  |  | 	workspace string | 
					
						
							|  |  |  | 	stack     *node.Node | 
					
						
							| 
									
										
										
										
											2016-06-30 13:03:26 +03:00
										 |  |  | 	ethereum  *eth.Ethereum | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	console   *Console | 
					
						
							|  |  |  | 	input     *hookedPrompter | 
					
						
							|  |  |  | 	output    *bytes.Buffer | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // newTester creates a test environment based on which the console can operate. | 
					
						
							|  |  |  | // Please ensure you call Close() on the returned tester to avoid leaks. | 
					
						
							|  |  |  | func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { | 
					
						
							|  |  |  | 	// Create a temporary storage for the node keys and initialize it | 
					
						
							|  |  |  | 	workspace, err := ioutil.TempDir("", "console-tester-") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("failed to create temporary keystore: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create a networkless protocol stack and start an Ethereum service within | 
					
						
							| 
									
										
										
										
											2017-04-12 16:27:23 +02:00
										 |  |  | 	stack, err := node.New(&node.Config{DataDir: workspace, UseLightweightKDF: true, Name: testInstance}) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("failed to create node: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ethConf := ð.Config{ | 
					
						
							| 
									
										
										
										
											2019-04-23 15:08:51 +08:00
										 |  |  | 		Genesis: core.DeveloperGenesisBlock(15, common.Address{}), | 
					
						
							|  |  |  | 		Miner: miner.Config{ | 
					
						
							|  |  |  | 			Etherbase: common.HexToAddress(testAddress), | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2017-11-24 22:10:27 +08:00
										 |  |  | 		Ethash: ethash.Config{ | 
					
						
							|  |  |  | 			PowMode: ethash.ModeTest, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if confOverride != nil { | 
					
						
							|  |  |  | 		confOverride(ethConf) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("failed to register Ethereum protocol: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Start the node and assemble the JavaScript console around it | 
					
						
							|  |  |  | 	if err = stack.Start(); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("failed to start test stack: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	client, err := stack.Attach() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("failed to attach to node: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	prompter := &hookedPrompter{scheduler: make(chan string)} | 
					
						
							|  |  |  | 	printer := new(bytes.Buffer) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	console, err := New(Config{ | 
					
						
							|  |  |  | 		DataDir:  stack.DataDir(), | 
					
						
							|  |  |  | 		DocRoot:  "testdata", | 
					
						
							|  |  |  | 		Client:   client, | 
					
						
							|  |  |  | 		Prompter: prompter, | 
					
						
							|  |  |  | 		Printer:  printer, | 
					
						
							|  |  |  | 		Preload:  []string{"preload.js"}, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("failed to create JavaScript console: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Create the final tester and return | 
					
						
							| 
									
										
										
										
											2016-06-30 13:03:26 +03:00
										 |  |  | 	var ethereum *eth.Ethereum | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	stack.Service(ðereum) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &tester{ | 
					
						
							|  |  |  | 		workspace: workspace, | 
					
						
							|  |  |  | 		stack:     stack, | 
					
						
							|  |  |  | 		ethereum:  ethereum, | 
					
						
							|  |  |  | 		console:   console, | 
					
						
							|  |  |  | 		input:     prompter, | 
					
						
							|  |  |  | 		output:    printer, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Close cleans up any temporary data folders and held resources. | 
					
						
							|  |  |  | func (env *tester) Close(t *testing.T) { | 
					
						
							|  |  |  | 	if err := env.console.Stop(false); err != nil { | 
					
						
							|  |  |  | 		t.Errorf("failed to stop embedded console: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-02-07 11:40:36 +01:00
										 |  |  | 	if err := env.stack.Close(); err != nil { | 
					
						
							|  |  |  | 		t.Errorf("failed to tear down embedded node: %v", err) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	os.RemoveAll(env.workspace) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Tests that the node lists the correct welcome message, notably that it contains | 
					
						
							|  |  |  | // the instance name, coinbase account, block number, data directory and supported | 
					
						
							|  |  |  | // console modules. | 
					
						
							|  |  |  | func TestWelcome(t *testing.T) { | 
					
						
							|  |  |  | 	tester := newTester(t, nil) | 
					
						
							|  |  |  | 	defer tester.Close(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tester.console.Welcome() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-12 18:05:47 +00:00
										 |  |  | 	output := tester.output.String() | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	if want := "Welcome"; !strings.Contains(output, want) { | 
					
						
							|  |  |  | 		t.Fatalf("console output missing welcome message: have\n%s\nwant also %s", output, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if want := fmt.Sprintf("instance: %s", testInstance); !strings.Contains(output, want) { | 
					
						
							|  |  |  | 		t.Fatalf("console output missing instance: have\n%s\nwant also %s", output, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if want := fmt.Sprintf("coinbase: %s", testAddress); !strings.Contains(output, want) { | 
					
						
							|  |  |  | 		t.Fatalf("console output missing coinbase: have\n%s\nwant also %s", output, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if want := "at block: 0"; !strings.Contains(output, want) { | 
					
						
							|  |  |  | 		t.Fatalf("console output missing sync status: have\n%s\nwant also %s", output, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if want := fmt.Sprintf("datadir: %s", tester.workspace); !strings.Contains(output, want) { | 
					
						
							|  |  |  | 		t.Fatalf("console output missing coinbase: have\n%s\nwant also %s", output, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Tests that JavaScript statement evaluation works as intended. | 
					
						
							|  |  |  | func TestEvaluate(t *testing.T) { | 
					
						
							|  |  |  | 	tester := newTester(t, nil) | 
					
						
							|  |  |  | 	defer tester.Close(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tester.console.Evaluate("2 + 2") | 
					
						
							| 
									
										
										
										
											2017-12-12 18:05:47 +00:00
										 |  |  | 	if output := tester.output.String(); !strings.Contains(output, "4") { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		t.Fatalf("statement evaluation failed: have %s, want %s", output, "4") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Tests that the console can be used in interactive mode. | 
					
						
							|  |  |  | func TestInteractive(t *testing.T) { | 
					
						
							|  |  |  | 	// Create a tester and run an interactive console in the background | 
					
						
							|  |  |  | 	tester := newTester(t, nil) | 
					
						
							|  |  |  | 	defer tester.Close(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go tester.console.Interactive() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-18 06:07:20 +09:00
										 |  |  | 	// Wait for a prompt and send a statement back | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	select { | 
					
						
							|  |  |  | 	case <-tester.input.scheduler: | 
					
						
							|  |  |  | 	case <-time.After(time.Second): | 
					
						
							|  |  |  | 		t.Fatalf("initial prompt timeout") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case tester.input.scheduler <- "2+2": | 
					
						
							|  |  |  | 	case <-time.After(time.Second): | 
					
						
							|  |  |  | 		t.Fatalf("input feedback timeout") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-18 06:07:20 +09:00
										 |  |  | 	// Wait for the second prompt and ensure first statement was evaluated | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	select { | 
					
						
							|  |  |  | 	case <-tester.input.scheduler: | 
					
						
							|  |  |  | 	case <-time.After(time.Second): | 
					
						
							|  |  |  | 		t.Fatalf("secondary prompt timeout") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-12-12 18:05:47 +00:00
										 |  |  | 	if output := tester.output.String(); !strings.Contains(output, "4") { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		t.Fatalf("statement evaluation failed: have %s, want %s", output, "4") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Tests that preloaded JavaScript files have been executed before user is given | 
					
						
							|  |  |  | // input. | 
					
						
							|  |  |  | func TestPreload(t *testing.T) { | 
					
						
							|  |  |  | 	tester := newTester(t, nil) | 
					
						
							|  |  |  | 	defer tester.Close(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tester.console.Evaluate("preloaded") | 
					
						
							| 
									
										
										
										
											2017-12-12 18:05:47 +00:00
										 |  |  | 	if output := tester.output.String(); !strings.Contains(output, "some-preloaded-string") { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		t.Fatalf("preloaded variable missing: have %s, want %s", output, "some-preloaded-string") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Tests that JavaScript scripts can be executes from the configured asset path. | 
					
						
							|  |  |  | func TestExecute(t *testing.T) { | 
					
						
							|  |  |  | 	tester := newTester(t, nil) | 
					
						
							|  |  |  | 	defer tester.Close(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tester.console.Execute("exec.js") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tester.console.Evaluate("execed") | 
					
						
							| 
									
										
										
										
											2017-12-12 18:05:47 +00:00
										 |  |  | 	if output := tester.output.String(); !strings.Contains(output, "some-executed-string") { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		t.Fatalf("execed variable missing: have %s, want %s", output, "some-executed-string") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Tests that the JavaScript objects returned by statement executions are properly | 
					
						
							| 
									
										
										
										
											2018-08-18 06:07:20 +09:00
										 |  |  | // pretty printed instead of just displaying "[object]". | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | func TestPrettyPrint(t *testing.T) { | 
					
						
							|  |  |  | 	tester := newTester(t, nil) | 
					
						
							|  |  |  | 	defer tester.Close(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tester.console.Evaluate("obj = {int: 1, string: 'two', list: [3, 3, 3], obj: {null: null, func: function(){}}}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Define some specially formatted fields | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		one   = jsre.NumberColor("1") | 
					
						
							|  |  |  | 		two   = jsre.StringColor("\"two\"") | 
					
						
							|  |  |  | 		three = jsre.NumberColor("3") | 
					
						
							|  |  |  | 		null  = jsre.SpecialColor("null") | 
					
						
							|  |  |  | 		fun   = jsre.FunctionColor("function()") | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	// Assemble the actual output we're after and verify | 
					
						
							|  |  |  | 	want := `{ | 
					
						
							|  |  |  |   int: ` + one + `, | 
					
						
							|  |  |  |   list: [` + three + `, ` + three + `, ` + three + `], | 
					
						
							|  |  |  |   obj: { | 
					
						
							|  |  |  |     null: ` + null + `, | 
					
						
							|  |  |  |     func: ` + fun + ` | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   string: ` + two + ` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ` | 
					
						
							| 
									
										
										
										
											2017-12-12 18:05:47 +00:00
										 |  |  | 	if output := tester.output.String(); output != want { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		t.Fatalf("pretty print mismatch: have %s, want %s", output, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-05-11 17:28:29 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Tests that the JavaScript exceptions are properly formatted and colored. | 
					
						
							|  |  |  | func TestPrettyError(t *testing.T) { | 
					
						
							|  |  |  | 	tester := newTester(t, nil) | 
					
						
							|  |  |  | 	defer tester.Close(t) | 
					
						
							|  |  |  | 	tester.console.Evaluate("throw 'hello'") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	want := jsre.ErrorColor("hello") + "\n\tat <eval>:1:7(1)\n\n" | 
					
						
							| 
									
										
										
										
											2017-12-12 18:05:47 +00:00
										 |  |  | 	if output := tester.output.String(); output != want { | 
					
						
							| 
									
										
										
										
											2016-05-11 17:28:29 +03:00
										 |  |  | 		t.Fatalf("pretty error mismatch: have %s, want %s", output, want) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-06-08 12:17:38 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Tests that tests if the number of indents for JS input is calculated correct. | 
					
						
							|  |  |  | func TestIndenting(t *testing.T) { | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		input               string | 
					
						
							|  |  |  | 		expectedIndentCount int | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{`var a = 1;`, 0}, | 
					
						
							|  |  |  | 		{`"some string"`, 0}, | 
					
						
							| 
									
										
										
										
											2018-08-18 06:07:20 +09:00
										 |  |  | 		{`"some string with (parenthesis`, 0}, | 
					
						
							| 
									
										
										
										
											2016-06-08 12:17:38 +02:00
										 |  |  | 		{`"some string with newline | 
					
						
							|  |  |  | 		("`, 0}, | 
					
						
							|  |  |  | 		{`function v(a,b) {}`, 0}, | 
					
						
							|  |  |  | 		{`function f(a,b) { var str = "asd("; };`, 0}, | 
					
						
							|  |  |  | 		{`function f(a) {`, 1}, | 
					
						
							|  |  |  | 		{`function f(a, function(b) {`, 2}, | 
					
						
							|  |  |  | 		{`function f(a, function(b) { | 
					
						
							|  |  |  | 		     var str = "a)}"; | 
					
						
							|  |  |  | 		  });`, 0}, | 
					
						
							|  |  |  | 		{`function f(a,b) { | 
					
						
							|  |  |  | 		   var str = "a{b(" + a, ", " + b; | 
					
						
							|  |  |  | 		   }`, 0}, | 
					
						
							|  |  |  | 		{`var str = "\"{"`, 0}, | 
					
						
							|  |  |  | 		{`var str = "'("`, 0}, | 
					
						
							|  |  |  | 		{`var str = "\\{"`, 0}, | 
					
						
							|  |  |  | 		{`var str = "\\\\{"`, 0}, | 
					
						
							|  |  |  | 		{`var str = 'a"{`, 0}, | 
					
						
							|  |  |  | 		{`var obj = {`, 1}, | 
					
						
							|  |  |  | 		{`var obj = { {a:1`, 2}, | 
					
						
							|  |  |  | 		{`var obj = { {a:1}`, 1}, | 
					
						
							|  |  |  | 		{`var obj = { {a:1}, b:2}`, 0}, | 
					
						
							|  |  |  | 		{`var obj = {}`, 0}, | 
					
						
							|  |  |  | 		{`var obj = { | 
					
						
							|  |  |  | 			a: 1, b: 2 | 
					
						
							|  |  |  | 		}`, 0}, | 
					
						
							|  |  |  | 		{`var test = }`, -1}, | 
					
						
							|  |  |  | 		{`var str = "a\""; var obj = {`, 1}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, tt := range testCases { | 
					
						
							|  |  |  | 		counted := countIndents(tt.input) | 
					
						
							|  |  |  | 		if counted != tt.expectedIndentCount { | 
					
						
							|  |  |  | 			t.Errorf("test %d: invalid indenting: have %d, want %d", i, counted, tt.expectedIndentCount) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |