/// <summary>
            /// Makes a tree that represents an observed input on a path into a subtree of
            /// a tree that represents the corresponding path in the pathset.
            ///
            /// <see cref="MergeStrongFingerprintAndPathSetTrees(JsonNode, JsonNode)"/>
            /// for numbering explanation.
            ///
            /// Converts
            /// [3] "E":"VSO0:E0C5007DC8CF2D331236F156F136C50CACE2A5D549CD132D9B44ABD1F13D50CC00"
            /// =>
            /// [3'] "ObservedInput":"E:VSO0:E0C5007DC8CF2D331236F156F136C50CACE2A5D549CD132D9B44ABD1F13D50CC00"
            ///
            /// Reparent [3'] from
            /// [2] "ObservedInputs":""
            /// to
            /// [5'] "B:/out/objects/n/x/qbkexxlc8je93wycw7yrlw0a305n7k/xunit-out/CacheMissAnaAD836B23/3/obj/readonly/src_0":""
            ///
            /// Add
            /// [8] "Members":"[src_1, src_2]"
            /// to
            /// [5'] "B:/out/objects/n/x/qbkexxlc8je93wycw7yrlw0a305n7k/xunit-out/CacheMissAnaAD836B23/3/obj/readonly/src_0":""
            /// </summary>
            /// <param name="observedInputNode"></param>
            /// <param name="pathSetNode"></param>
            private void ReparentObservedInput(JsonNode observedInputNode, JsonNode pathSetNode)
            {
                // Store values from
                // [3] "E":"VSO0:E0C5007DC8CF2D331236F156F136C50CACE2A5D549CD132D9B44ABD1F13D50CC00"
                // before manipulating the node
                var observedInputType = observedInputNode.Name;
                var observedInputHash = observedInputNode.Values[0];

                var values = observedInputNode.Values;

                values.Clear();

                switch (observedInputType)
                {
                case ObservedInputConstants.AbsentPathProbe:
                    values.Add(ObservedInputType.AbsentPathProbe.ToString());
                    break;

                case ObservedInputConstants.FileContentRead:
                    values.Add($"{ObservedInputType.FileContentRead.ToString()}:{observedInputHash}");
                    break;

                case ObservedInputConstants.DirectoryEnumeration:
                    values.Add($"{ObservedInputType.DirectoryEnumeration.ToString()}:{observedInputHash}");
                    // [8] "Members":"[src_1, src_2]"
                    AddDirectoryMembershipBranch(observedInputHash, pathSetNode);
                    break;

                case ObservedInputConstants.ExistingDirectoryProbe:
                    values.Add(ObservedInputType.ExistingDirectoryProbe.ToString());
                    break;

                case ObservedInputConstants.ExistingFileProbe:
                    values.Add(ObservedInputType.ExistingFileProbe.ToString());
                    break;
                }

                // [3'] "ObservedInput":"E:VSO0:E0C5007DC8CF2D331236F156F136C50CACE2A5D549CD132D9B44ABD1F13D50CC00"
                observedInputNode.Name = ObservedInputConstants.ObservedInputs;
                JsonTree.ReparentBranch(observedInputNode, pathSetNode);
            }
            /// <summary>
            /// Adds a directory membership tree as a sub-tree to a given path set tree.
            /// </summary>
            /// <param name="directoryFingerprint">
            /// The directory fingerprint to look up membership.
            /// </param>
            /// <param name="pathSetNode">
            /// The path set node that represents the directory and the parent node.
            /// </param>
            private void AddDirectoryMembershipBranch(string directoryFingerprint, JsonNode pathSetNode)
            {
                if (m_store.TryGetContentHashValue(directoryFingerprint, out string inputs))
                {
                    WriteToPipFile(PrettyFormatJsonField(new KeyValuePair <string, string>(directoryFingerprint, inputs)).ToString());

                    var directoryMembershipTree = JsonTree.Deserialize(inputs);
                    for (var it = directoryMembershipTree.Children.First; it != null; it = it.Next)
                    {
                        JsonTree.ReparentBranch(it.Value, pathSetNode);
                    }
                }
                else
                {
                    // Include a node for the directory membership, but use an error message as the value
                    var placeholder = new JsonNode
                    {
                        Name = directoryFingerprint
                    };
                    placeholder.Values.Add(CacheMissAnalysisUtilities.RepeatedStrings.MissingDirectoryMembershipFingerprint);

                    JsonTree.ReparentBranch(placeholder, pathSetNode);
                }
            }
            /// <summary>
            /// Path set hash inputs are stored separately from the strong fingerprint inputs.
            /// This merges the path set hash inputs tree into the strong fingerprint inputs tree
            /// while maintaining the 1:1 relationship between the path set and observed inputs.
            ///
            /// Node notation:
            /// [id] "{name}":"{value}"
            /// Tree notation:
            /// {parentNode}
            ///     {childNode}
            ///
            /// Start with the following subtrees:
            ///
            /// From strong fingerprint
            ///
            /// [1] "PathSet":"VSO0:7E2E49845EC0AE7413519E3EE605272078AF0B1C2911C021681D1D9197CC134A00"
            /// [2] "ObservedInputs":""
            ///     [3] "E":"VSO0:E0C5007DC8CF2D331236F156F136C50CACE2A5D549CD132D9B44ABD1F13D50CC00",
            ///
            /// From path set hash
            ///
            /// [4] "PathSet":""
            ///     [5] "Path":"B:/out/objects/n/x/qbkexxlc8je93wycw7yrlw0a305n7k/xunit-out/CacheMissAnaAD836B23/3/obj/readonly/src_0"
            ///     [6] "Flags":"IsDirectoryPath, DirectoryEnumeration, DirectoryEnumerationWithAllPattern"
            ///     [7] "EnumeratePatternRegex":"^.*$"
            ///
            /// And end with:
            ///
            /// [1] "PathSet":"VSO0:7E2E49845EC0AE7413519E3EE605272078AF0B1C2911C021681D1D9197CC134A00"
            ///     [5'] "B:/out/objects/n/x/qbkexxlc8je93wycw7yrlw0a305n7k/xunit-out/CacheMissAnaAD836B23/3/obj/readonly/src_0":""
            ///         [6'] "Flags":"IsDirectoryPath, DirectoryEnumeration, DirectoryEnumerationWithAllPattern"
            ///         [7'] "EnumeratePatternRegex":"^.*$"
            ///         [3'] "ObservedInput":"E:VSO0:E0C5007DC8CF2D331236F156F136C50CACE2A5D549CD132D9B44ABD1F13D50CC00"
            ///         [8] "Members":"[src_1, src_2]"
            /// </summary>
            /// <returns>
            /// The root node of the merged tree (which will be the strong fingerprint tree's root).
            /// </returns>
            private JsonNode MergeStrongFingerprintAndPathSetTrees(JsonNode strongFingerprintTree, JsonNode pathSetTree)
            {
                // [1] "PathSet":"VSO0:7E2E49845EC0AE7413519E3EE605272078AF0B1C2911C021681D1D9197CC134A00")
                var parentPathNode = JsonTree.FindNodeByName(strongFingerprintTree, ObservedPathEntryConstants.PathSet);

                // [2] "ObservedInputs":""
                var observedInputsNode = JsonTree.FindNodeByName(strongFingerprintTree, ObservedInputConstants.ObservedInputs);

                JsonTree.EmancipateBranch(observedInputsNode);

                // In preparation for merging with observed inputs nodes,
                // remove the path set node's branch from the path set tree
                // [4] "PathSet":""
                var pathSetNode = JsonTree.FindNodeByName(pathSetTree, ObservedPathEntryConstants.PathSet);

                JsonTree.EmancipateBranch(pathSetNode);

                JsonNode currPathNode    = null;
                JsonNode currFlagNode    = null;
                JsonNode currRegexNode   = null;
                var      observedInputIt = observedInputsNode.Children.First;

                for (var it = pathSetNode.Children.First; it != null; it = pathSetNode.Children.First)
                {
                    var child = it.Value;
                    switch (child.Name)
                    {
                    case ObservedPathEntryConstants.Path:
                        currPathNode = child;
                        // Switch from literal string "path" to actual file system path
                        // [5'] "B:/out/objects/n/x/qbkexxlc8je93wycw7yrlw0a305n7k/xunit-out/CacheMissAnaAD836B23/3/obj/readonly/src_0":""
                        currPathNode.Name = currPathNode.Values[0];
                        // The name captures the node's value, so clear the values to avoid extraneous value comparison when diffing
                        currPathNode.Values.Clear();
                        JsonTree.ReparentBranch(currPathNode, parentPathNode);

                        // [6'] "Flags":"IsDirectoryPath, DirectoryEnumeration, DirectoryEnumerationWithAllPattern"
                        JsonTree.ReparentBranch(currFlagNode, currPathNode);
                        // [7'] "EnumeratePatternRegex":"^.*$"
                        JsonTree.ReparentBranch(currRegexNode, currPathNode);

                        // [3'] "ObservedInput":"E:VSO0:E0C5007DC8CF2D331236F156F136C50CACE2A5D549CD132D9B44ABD1F13D50CC00"
                        // [8] "Members":"[src_1, src_2]"
                        ReparentObservedInput(observedInputIt.Value, currPathNode);
                        observedInputIt = observedInputsNode.Children.First;
                        break;

                    case ObservedPathEntryConstants.Flags:
                        // [6] "Flags":"IsDirectoryPath, DirectoryEnumeration, DirectoryEnumerationWithAllPattern"
                        currFlagNode = child;
                        JsonTree.EmancipateBranch(currFlagNode);
                        break;

                    case ObservedPathEntryConstants.EnumeratePatternRegex:
                        // [7] "EnumeratePatternRegex":"^.*$"
                        currRegexNode = child;
                        JsonTree.EmancipateBranch(currRegexNode);
                        break;

                    default:
                        break;
                    }
                }

                // Re-parent any other branches of the path set tree to the strong fingerprint tree
                // so they are still in a full strong fingerprint tree comparison.
                // We re-parent under parentPathNode because branches of pathSetTree are elements of PathSet
                var node = pathSetTree.Children.First;

                while (node != null)
                {
                    JsonTree.ReparentBranch(node.Value, parentPathNode);
                    node = pathSetTree.Children.First;
                }

                return(strongFingerprintTree);
            }