Fix Lockfile Schematics
This commit is contained in:
+107
-2
@@ -1,6 +1,7 @@
|
||||
package topdata
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -3600,17 +3601,121 @@ func saveLockfile(path string, lockData map[string]int) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
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 {
|
||||
return fmt.Errorf("marshal lockfile: %w", err)
|
||||
}
|
||||
raw = append(raw, '\n')
|
||||
if err := os.WriteFile(path, raw, 0o644); err != nil {
|
||||
return fmt.Errorf("write lockfile %s: %w", path, err)
|
||||
}
|
||||
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) {
|
||||
info, err := os.Stat(root)
|
||||
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) {
|
||||
_, err := newResolvedTableRegistry([]nativeCollectedDataset{
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user