[gndoc] Fix gndoc unstable output

This change adds an additional sort step when gndoc iterates over
a map. It should fix the gndoc output instability issue.

Bug: TC-445
Tests: Local
Change-Id: Ie4a06c135a1c340873386cfc30e606d66252235e
diff --git a/gndoc/argmap.go b/gndoc/argmap.go
index da3bd20..24888a6 100644
--- a/gndoc/argmap.go
+++ b/gndoc/argmap.go
@@ -26,68 +26,74 @@
 	}
 }
 
-// AddArg creates args from GNArgs and adds them to the maps
+// AddArgs creates args from GNArgs and adds them to the maps
 func (a *ArgMap) AddArgs(gnArgs <-chan Arg) {
 	for gnArg := range gnArgs {
 		a.AddArg(gnArg)
 	}
 }
 
+// AddArg adds Arg to the maps
 func (a *ArgMap) AddArg(gnArg Arg) {
 	a.args[gnArg.Key] = append(a.args[gnArg.Key], gnArg)
 	a.argLookup[gnArg.Name] = append(a.argLookup[gnArg.Name], gnArg.Key)
 }
 
-// sortedArgs returns the list of args in the appropriate order to print.
-func (a *ArgMap) sortedArgs() (map[string]map[string][]Arg, []string) {
+// EmitMarkdown emits Markdown text for the map of arguments.
+func (a *ArgMap) EmitMarkdown(out io.Writer) {
+	type mappedArgs struct {
+		m map[string][]Arg
+		k []string
+	}
+	sortedArgs := struct {
+		m map[string]*mappedArgs
+		k []string
+	}{
+		m: make(map[string]*mappedArgs),
+		k: make([]string, 0),
+	}
+
 	numKeys := len(a.args)
-	args := make(map[string]map[string][]Arg)
-	// For each arg, we need to push it into the appropiate list.
 	for _, gnArgs := range a.args {
 		for _, gnArg := range gnArgs {
 			// Lookup the keys associated with this arg & sort & stringify.
 			keys, _ := a.argLookup[gnArg.Name]
 			if len(keys) == numKeys {
-				// Incoming keys will always have an `=`, and so this is an okay value.
+				// Incoming keys will always have an `=`, and so  this is an
+				// okay value.
 				keys = []string{"all"}
 			}
+			sort.Strings(keys)
 			key := strings.Join(keys, ", ")
-			_, ok := args[key]
-			if !ok {
-				args[key] = make(map[string][]Arg)
+			if _, ok := sortedArgs.m[key]; !ok {
+				sortedArgs.m[key] = &mappedArgs{
+					m: make(map[string][]Arg),
+					k: make([]string, 0)}
 			}
-			args[key][gnArg.Name] = append(args[key][gnArg.Name], gnArg)
+			sortedArgs.m[key].m[gnArg.Name] = append(sortedArgs.m[key].m[gnArg.Name], gnArg)
 		}
 	}
-
-	// Get the keys in alphabetical order
-	keys := make([]string, 0, numKeys)
-	for k := range args {
-		// Sort by name first, then by key
-		for argName := range args[k] {
-			sort.Slice(args[k][argName], func(i, j int) bool { return args[k][argName][i].Key < args[k][argName][j].Key })
+	for k := range sortedArgs.m {
+		for argName := range sortedArgs.m[k].m {
+			sort.Slice(sortedArgs.m[k].m[argName], func(i, j int) bool {
+				return sortedArgs.m[k].m[argName][i].Key < sortedArgs.m[k].m[argName][j].Key
+			})
+			sortedArgs.m[k].k = append(sortedArgs.m[k].k, argName)
 		}
-
-		keys = append(keys, k)
+		sort.Strings(sortedArgs.m[k].k)
+		sortedArgs.k = append(sortedArgs.k, k)
 	}
-	sort.Strings(keys)
-
-	return args, keys
-}
-
-// EmitMarkdown emits Markdown text for the map of arguments.
-func (a *ArgMap) EmitMarkdown(out io.Writer) {
+	sort.Strings(sortedArgs.k)
 	// Emit a header.
 	fmt.Fprintf(out, "# %s\n\n", pageTitle)
-	gnArgsMap, keys := a.sortedArgs()
-	for _, name := range keys {
+	for _, name := range sortedArgs.k {
 		if name == "all" {
 			fmt.Fprintf(out, "## All builds\n\n")
 		} else {
 			fmt.Fprintf(out, "## `%s`\n\n", name)
 		}
-
-		for _, gnArgs := range gnArgsMap[name] {
+		for _, argsKey := range sortedArgs.m[name].k {
+			gnArgs := sortedArgs.m[name].m[argsKey]
 			writeArgs(gnArgs, out, a.sources)
 		}
 	}
diff --git a/gndoc/argmap_test.go b/gndoc/argmap_test.go
new file mode 100644
index 0000000..005c97b
--- /dev/null
+++ b/gndoc/argmap_test.go
@@ -0,0 +1,136 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package gndoc
+
+import (
+	"bytes"
+	"math/rand"
+	"testing"
+	"time"
+)
+
+func genArgMapInRandomOrder() *ArgMap {
+	testArgMap := NewArgMap(Sources())
+	gnArgs := []Arg{
+		defaultx64Arg,
+		defaultarm64Arg,
+		defaultarm64ArgWithCurrent,
+		defaultx64ArgWithCurrent,
+		x64Arg,
+		arm64Arg,
+		twoKeyarm64TestArg,
+		twoKeyx64TestArg,
+		twoKeyarm64OtherArg,
+		twoKeyx64OtherArg,
+		newLineValueArg,
+	}
+	// Shuffle the gnArgs.
+	r := rand.New(rand.NewSource(time.Now().Unix()))
+	shuffledGnArgs := make([]Arg, 0)
+	for _, j := range r.Perm(len(gnArgs)) {
+		shuffledGnArgs = append(shuffledGnArgs, gnArgs[j])
+	}
+	for _, arg := range shuffledGnArgs {
+		testArgMap.AddArg(arg)
+	}
+	return testArgMap
+}
+
+func TestArgMapEmitMarkdown(t *testing.T) {
+	expectedOutput := `# GN Build Arguments
+
+## ` + "`target_cpu = arm64, package='other/package/default'`" + `
+
+### arm64Other
+Description of arm64 arg.
+
+**Current value for ` + "`target_cpu = arm64, package='other/package/default'`:** `arg`" + `
+
+**Overridden from the default:** ` + "`value`" + `
+
+## ` + "`target_cpu = arm64, target_cpu = arm64, package='test/package/default'`" + `
+
+### arm64
+Description of arm64 arg.
+
+**Current value for ` + "`target_cpu = arm64`:** `arg`" + `
+
+**Overridden from the default:** ` + "`value`" + `
+
+**Current value for ` + "`target_cpu = arm64, package='test/package/default'`:** `arg`" + `
+
+**Overridden from the default:** ` + "`value`" + `
+
+## ` + "`target_cpu = arm64, target_cpu = x64`" + `
+
+### default
+Description of default arg.
+
+**Current value (from the default):** ` + "`false`" + `
+
+From //test/BUILD.gn:2
+
+### default_current
+Description of default_current arg.
+
+**Current value for ` + "`target_cpu = arm64`:** `[1, 2]`" + `
+
+From [//build/BUILD.gn:24](http://fuchsia.com/build/BUILD.gn#24)
+
+**Overridden from the default:** ` + "`[3, 4]`" + `
+
+From [//base/BUILD.gn:4](http://fuchsia.com/base/BUILD.gn#4)
+
+**Current value for ` + "`target_cpu = x64`:** `3`" + `
+
+From [//build/BUILD.gn:24](http://fuchsia.com/build/BUILD.gn#24)
+
+**Overridden from the default:** ` + "`4`" + `
+
+From [//base/BUILD.gn:2](http://fuchsia.com/base/BUILD.gn#2)
+
+## ` + "`target_cpu = x64, package='other/package/default'`" + `
+
+### NewLine
+Description of newline arg.
+
+**Current value (from the default):**
+` + "```" + `
+{
+  base = "//build/toolchain/fuchsia:x64"
+}
+` + "```" + `
+
+### x64Other
+Description of x64 arg.
+
+**Current value for ` + "`target_cpu = x64, package='other/package/default'`:** `arg`" + `
+
+**Overridden from the default:** ` + "`value`" + `
+
+## ` + "`target_cpu = x64, target_cpu = x64, package='test/package/default'`" + `
+
+### x64
+Description of x64 arg that references [//build/path.py](http://fuchsia.com/build/path.py), //sources, and [//base](http://fuchsia.com/base).
+
+**Current value for ` + "`target_cpu = x64`:** `1`" + `
+
+**Overridden from the default:** ` + "`2`" + `
+
+**Current value for ` + "`target_cpu = x64, package='test/package/default'`:** `arg`" + `
+
+**Overridden from the default:** ` + "`value`" + `
+
+`
+	for i := 0; i < 10; i++ {
+		testArgMap := genArgMapInRandomOrder()
+		var testOutput bytes.Buffer
+		testArgMap.EmitMarkdown(&testOutput)
+		if expectedOutput != testOutput.String() {
+			t.Errorf("expecting output:\n%s\n, got:\n%s\n", expectedOutput, testOutput.String())
+			break
+		}
+	}
+}