mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Tame a few tests that relied on pairs being somewhat deterministic (#6371)
* Mangle stupid defaults test so that it compares tables, and not a non-deterministic string representation of one. It's still extremely dumb and annoying to update. (i.e., feel free to kill it with fire in a subsequent PR, I think everybody would cheer). * Rewrite DepGraph to be deterministic i.e., fully array based, no more hashes, which means no more pairs randomly re-ordering stuff. Insertion order is now preserved. Pretty sure a couple of bugs have been fixed and/or added along the way ;p. * Resync frontend/apps/filemanager/lib/md.lua w/ upstream And use orderedPairs in the attribute parsing code, just to make that stupid test happy.
This commit is contained in:
@@ -11,6 +11,8 @@ Example:
|
||||
-- The return value of dg:serialize() will be:
|
||||
-- {'a2', 'c1', 'b1', 'a1'}
|
||||
|
||||
NOTE: Insertion order is preserved, duplicates are automatically prevented (both as main nodes and as deps).
|
||||
|
||||
]]
|
||||
|
||||
local DepGraph = {}
|
||||
@@ -23,97 +25,217 @@ function DepGraph:new(new_o)
|
||||
return o
|
||||
end
|
||||
|
||||
function DepGraph:addNode(node_key, deps)
|
||||
if not self.nodes[node_key] then
|
||||
self.nodes[node_key] = {}
|
||||
-- Check if node exists, and is active
|
||||
function DepGraph:checkNode(id)
|
||||
for _, n in ipairs(self.nodes) do
|
||||
if n.key == id and not n.disabled then
|
||||
return true
|
||||
end
|
||||
end
|
||||
if not deps then return end
|
||||
|
||||
local node_deps = {}
|
||||
for _,dep_node_key in ipairs(deps) do
|
||||
if not self.nodes[dep_node_key] then
|
||||
self.nodes[dep_node_key] = {}
|
||||
end
|
||||
table.insert(node_deps, dep_node_key)
|
||||
end
|
||||
self.nodes[node_key].deps = node_deps
|
||||
return false
|
||||
end
|
||||
|
||||
function DepGraph:removeNode(node_key)
|
||||
-- We should not remove it from self.nodes if it has
|
||||
-- a .deps array (it is the other nodes, that had this
|
||||
-- one in their override=, that have added themselves in
|
||||
-- this node's .deps). We don't want to lose these
|
||||
-- dependencies if we later re-addNode this node.
|
||||
local node = self.nodes[node_key]
|
||||
if node then
|
||||
if not node.deps or #node.deps == 0 then
|
||||
self.nodes[node_key] = nil
|
||||
-- Returns a (node, node_index) tuple
|
||||
function DepGraph:getNode(id)
|
||||
for i, n in ipairs(self.nodes) do
|
||||
if n.key == id then
|
||||
return n, i
|
||||
end
|
||||
end
|
||||
-- But we should remove it from the .deps of other nodes.
|
||||
for curr_node_key, curr_node in pairs(self.nodes) do
|
||||
if curr_node.deps then
|
||||
local remove_idx
|
||||
for idx, dep_node_key in ipairs(self.nodes) do
|
||||
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- Like getNode, but only for active nodes:
|
||||
-- if node is nil but index is set, node is disabled
|
||||
function DepGraph:getActiveNode(id)
|
||||
local node, index = self:getNode(id)
|
||||
if node and node.disabled then
|
||||
return nil, index
|
||||
end
|
||||
|
||||
return node, index
|
||||
end
|
||||
|
||||
-- Add a node, with an optional list of dependencies
|
||||
-- If dependencies don't exist as proper nodes yet, they'll be created, in order.
|
||||
-- If node already exists, the new list of dependencies is *appended* to the existing one, without duplicates.
|
||||
function DepGraph:addNode(node_key, deps)
|
||||
-- Find main node if it already exists
|
||||
local node = self:getNode(node_key)
|
||||
|
||||
if node then
|
||||
-- If it exists, but was disabled, re-enable it
|
||||
if node.disabled then
|
||||
node.disabled = nil
|
||||
end
|
||||
else
|
||||
-- If it doesn't exist at all, create it
|
||||
node = { key = node_key }
|
||||
table.insert(self.nodes, node)
|
||||
end
|
||||
|
||||
-- No dependencies? We're done!
|
||||
if not deps then
|
||||
return
|
||||
end
|
||||
|
||||
-- Create dep nodes if they don't already exist
|
||||
local node_deps = node.deps or {}
|
||||
for _, dep_node_key in ipairs(deps) do
|
||||
local dep_node = self:getNode(dep_node_key)
|
||||
|
||||
if dep_node then
|
||||
-- If it exists, but was disabled, re-enable it
|
||||
if dep_node.disabled then
|
||||
dep_node.disabled = nil
|
||||
end
|
||||
else
|
||||
-- Create dep node itself if need be
|
||||
dep_node = { key = dep_node_key }
|
||||
table.insert(self.nodes, dep_node)
|
||||
end
|
||||
|
||||
-- Update deps array the long way 'round, and prevent duplicates, in case deps was funky as hell.
|
||||
local exists = false
|
||||
for _, k in ipairs(node_deps) do
|
||||
if k == dep_node_key then
|
||||
exists = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not exists then
|
||||
table.insert(node_deps, dep_node_key)
|
||||
end
|
||||
end
|
||||
-- Update main node with its updated deps
|
||||
node.deps = node_deps
|
||||
end
|
||||
|
||||
-- Attempt to remove a node, as well as all traces of it from other nodes' deps
|
||||
-- If node has deps, it's kept, but marked as disabled, c.f., lenghty comment below.
|
||||
function DepGraph:removeNode(node_key)
|
||||
-- We shouldn't remove a node if it has dependencies (as these may have been added via addNodeDep
|
||||
-- (as opposed to the optional deps list passed to addNode), like what InputContainer does with overrides,
|
||||
-- overrides originating from completely *different* nodes,
|
||||
-- meaning those other nodes basically add themselves to another's deps).
|
||||
-- We don't want to lose the non-native dependency on these other nodes in case we later re-addNode this one
|
||||
-- with its stock dependency list.
|
||||
local node, index = self:getNode(node_key)
|
||||
if node then
|
||||
if not node.deps or #node.deps == 0 then
|
||||
-- No dependencies, can be wiped safely
|
||||
table.remove(self.nodes, index)
|
||||
else
|
||||
-- Can't remove it, just flag it as disabled instead
|
||||
node.disabled = true
|
||||
end
|
||||
end
|
||||
-- On the other hand, we definitely should remove it from the deps of every *other* node.
|
||||
for _, curr_node in ipairs(self.nodes) do
|
||||
-- Is not the to be removed node, and has deps
|
||||
if curr_node.key ~= node_key and curr_node.deps then
|
||||
-- Walk that node's deps to check if it depends on us
|
||||
for idx, dep_node_key in ipairs(curr_node.deps) do
|
||||
-- If it did, wipe ourselves from there
|
||||
if dep_node_key == node_key then
|
||||
remove_idx = idx
|
||||
table.remove(curr_node.deps, idx)
|
||||
break
|
||||
end
|
||||
end
|
||||
if remove_idx then table.remove(curr_node.deps, remove_idx) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DepGraph:checkNode(id)
|
||||
if self.nodes[id] then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Add a single dep_node_key to node_key's deps
|
||||
function DepGraph:addNodeDep(node_key, dep_node_key)
|
||||
local node = self.nodes[node_key]
|
||||
if not node then
|
||||
node = {}
|
||||
self.nodes[node_key] = node
|
||||
-- Check the main node
|
||||
local node = self:getNode(node_key)
|
||||
|
||||
if node then
|
||||
-- If it exists, but was disabled, re-enable it
|
||||
if node.disabled then
|
||||
node.disabled = nil
|
||||
end
|
||||
else
|
||||
-- If it doesn't exist at all, create it
|
||||
node = { key = node_key }
|
||||
table.insert(self.nodes, node)
|
||||
end
|
||||
|
||||
-- Then check the dep node
|
||||
local dep_node = self:getNode(dep_node_key)
|
||||
|
||||
if dep_node then
|
||||
-- If it exists, but was disabled, re-enable it
|
||||
if dep_node.disabled then
|
||||
dep_node.disabled = nil
|
||||
end
|
||||
else
|
||||
-- Create dep node itself if need be
|
||||
dep_node = { key = dep_node_key }
|
||||
table.insert(self.nodes, dep_node)
|
||||
end
|
||||
|
||||
-- If main node currently doesn't have deps, start with an empty array
|
||||
if not node.deps then
|
||||
node.deps = {}
|
||||
end
|
||||
|
||||
-- Prevent duplicate deps
|
||||
local exists = false
|
||||
for _, k in ipairs(node.deps) do
|
||||
if k == dep_node_key then
|
||||
exists = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not exists then
|
||||
table.insert(node.deps, dep_node_key)
|
||||
end
|
||||
if not node.deps then node.deps = {} end
|
||||
table.insert(node.deps, dep_node_key)
|
||||
end
|
||||
|
||||
-- Remove a single dep_node_key from node_key's deps
|
||||
function DepGraph:removeNodeDep(node_key, dep_node_key)
|
||||
local node = self.nodes[node_key]
|
||||
if not node.deps then node.deps = {} end
|
||||
for i, dep_key in ipairs(node.deps) do
|
||||
if dep_key == dep_node_key then
|
||||
self.nodes[node_key]["deps"][i] = nil
|
||||
local node = self:getNode(node_key)
|
||||
if node.deps then
|
||||
for idx, dep_key in ipairs(node.deps) do
|
||||
if dep_key == dep_node_key then
|
||||
table.remove(node.deps, idx)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Return a list (array) of node keys, ordered by insertion order and dependency.
|
||||
-- Dependencies come first (and are also ordered by insertion order themselves).
|
||||
function DepGraph:serialize()
|
||||
local visited = {}
|
||||
local ordered_nodes = {}
|
||||
for node_key,_ in pairs(self.nodes) do
|
||||
|
||||
for _, n in ipairs(self.nodes) do
|
||||
local node_key = n.key
|
||||
if not visited[node_key] then
|
||||
local queue = {node_key}
|
||||
local queue = { node_key }
|
||||
while #queue > 0 do
|
||||
local pos = #queue
|
||||
local curr_node_key = queue[pos]
|
||||
local curr_node = self.nodes[curr_node_key]
|
||||
local curr_node = self:getActiveNode(curr_node_key)
|
||||
local all_deps_visited = true
|
||||
if curr_node.deps then
|
||||
if curr_node and curr_node.deps then
|
||||
for _, dep_node_key in ipairs(curr_node.deps) do
|
||||
if not visited[dep_node_key] then
|
||||
-- only insert to queue for later process if node
|
||||
-- has dependencies
|
||||
if self.nodes[dep_node_key].deps then
|
||||
table.insert(queue, dep_node_key)
|
||||
else
|
||||
table.insert(ordered_nodes, dep_node_key)
|
||||
-- Only insert to queue for later process if node has dependencies
|
||||
local dep_node = self:getActiveNode(dep_node_key)
|
||||
-- Only if it was active!
|
||||
if dep_node then
|
||||
if dep_node.deps then
|
||||
table.insert(queue, dep_node_key)
|
||||
else
|
||||
table.insert(ordered_nodes, dep_node_key)
|
||||
end
|
||||
end
|
||||
visited[dep_node_key] = true
|
||||
all_deps_visited = false
|
||||
@@ -124,7 +246,10 @@ function DepGraph:serialize()
|
||||
if all_deps_visited then
|
||||
visited[curr_node_key] = true
|
||||
table.remove(queue, pos)
|
||||
table.insert(ordered_nodes, curr_node_key)
|
||||
-- Only if it was active!
|
||||
if curr_node then
|
||||
table.insert(ordered_nodes, curr_node_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user