Fix Lockfile Schematics
This commit is contained in:
+107
-2
@@ -1,6 +1,7 @@
|
|||||||
package topdata
|
package topdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -3600,17 +3601,121 @@ func saveLockfile(path string, lockData map[string]int) error {
|
|||||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
return fmt.Errorf("create lockfile parent: %w", err)
|
return fmt.Errorf("create lockfile parent: %w", err)
|
||||||
}
|
}
|
||||||
raw, err := json.MarshalIndent(lockData, "", " ")
|
keys, err := orderedLockfileKeys(path, lockData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read lockfile order: %w", err)
|
||||||
|
}
|
||||||
|
raw, err := marshalOrderedLockfile(keys, lockData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshal lockfile: %w", err)
|
return fmt.Errorf("marshal lockfile: %w", err)
|
||||||
}
|
}
|
||||||
raw = append(raw, '\n')
|
|
||||||
if err := os.WriteFile(path, raw, 0o644); err != nil {
|
if err := os.WriteFile(path, raw, 0o644); err != nil {
|
||||||
return fmt.Errorf("write lockfile %s: %w", path, err)
|
return fmt.Errorf("write lockfile %s: %w", path, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func orderedLockfileKeys(path string, lockData map[string]int) ([]string, error) {
|
||||||
|
existingKeys, err := readLockfileKeyOrder(path)
|
||||||
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(lockData))
|
||||||
|
seen := make(map[string]struct{}, len(lockData))
|
||||||
|
for _, key := range existingKeys {
|
||||||
|
if _, ok := lockData[key]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys = append(keys, key)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
extraKeys := make([]string, 0, len(lockData)-len(keys))
|
||||||
|
for key := range lockData {
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
extraKeys = append(extraKeys, key)
|
||||||
|
}
|
||||||
|
slices.Sort(extraKeys)
|
||||||
|
keys = append(keys, extraKeys...)
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLockfileKeyOrder(path string) ([]string, error) {
|
||||||
|
raw, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(raw))
|
||||||
|
token, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
delim, ok := token.(json.Delim)
|
||||||
|
if !ok || delim != '{' {
|
||||||
|
return nil, fmt.Errorf("%s must contain a JSON object", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := []string{}
|
||||||
|
for decoder.More() {
|
||||||
|
token, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key, ok := token.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s contains a non-string object key", path)
|
||||||
|
}
|
||||||
|
var value json.RawMessage
|
||||||
|
if err := decoder.Decode(&value); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err = decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
delim, ok = token.(json.Delim)
|
||||||
|
if !ok || delim != '}' {
|
||||||
|
return nil, fmt.Errorf("%s must contain a JSON object", path)
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalOrderedLockfile(keys []string, lockData map[string]int) ([]byte, error) {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return []byte("{}\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString("{\n")
|
||||||
|
for index, key := range keys {
|
||||||
|
keyRaw, err := json.Marshal(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
valueRaw, err := json.Marshal(lockData[key])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
buffer.Write(keyRaw)
|
||||||
|
buffer.WriteString(": ")
|
||||||
|
buffer.Write(valueRaw)
|
||||||
|
if index < len(keys)-1 {
|
||||||
|
buffer.WriteByte(',')
|
||||||
|
}
|
||||||
|
buffer.WriteByte('\n')
|
||||||
|
}
|
||||||
|
buffer.WriteString("}\n")
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadLegacyTLK(root string) (*legacyTLKData, error) {
|
func loadLegacyTLK(root string) (*legacyTLKData, error) {
|
||||||
info, err := os.Stat(root)
|
info, err := os.Stat(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,6 +108,29 @@ func TestResolvedTableRegistryRegistersConsistentTables(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSaveLockfilePreservesExistingOrderAndAppendsNewKeys(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
lockPath := filepath.Join(root, "lock.json")
|
||||||
|
writeFile(t, lockPath, "{\n \"b\": 2,\n \"a\": 1\n}\n")
|
||||||
|
|
||||||
|
if err := saveLockfile(lockPath, map[string]int{
|
||||||
|
"b": 2,
|
||||||
|
"a": 1,
|
||||||
|
"c": 3,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("saveLockfile failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := os.ReadFile(lockPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read lockfile: %v", err)
|
||||||
|
}
|
||||||
|
want := "{\n \"b\": 2,\n \"a\": 1,\n \"c\": 3\n}\n"
|
||||||
|
if string(got) != want {
|
||||||
|
t.Fatalf("unexpected lockfile contents:\n%s", string(got))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolvedTableRegistryRejectsDuplicateKeys(t *testing.T) {
|
func TestResolvedTableRegistryRejectsDuplicateKeys(t *testing.T) {
|
||||||
_, err := newResolvedTableRegistry([]nativeCollectedDataset{
|
_, err := newResolvedTableRegistry([]nativeCollectedDataset{
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user