1
This commit is contained in:
@@ -153,23 +153,28 @@ void main() {
|
||||
expect(m2.models.first.version, 'v2.0.0');
|
||||
});
|
||||
|
||||
test('autoUpdate:已激活模型出新版本,refresh 后自动重下并立即生效', () async {
|
||||
test('autoUpdate:已下载模型出新版本,自动补齐文件但不改变激活状态', () async {
|
||||
final m = manager(client([_item()]));
|
||||
await m.refresh();
|
||||
await m.downloadModel(m.catalog.first);
|
||||
await m.downloadModel(m.catalog.first); // (7, s) v1 下载并激活
|
||||
expect(downloadHits, 1);
|
||||
|
||||
// 重启 + 服务器目录出 v2:refresh 内部 autoUpdate 自动重下(无需手动)
|
||||
// 模拟新会话(新实例 + 服务器目录出 v2):文件仍在 → autoUpdate 只补文件
|
||||
final m2 = manager(client([_item(version: 'v2.0.0')]));
|
||||
await m2.refresh();
|
||||
for (var i = 0; i < 50 && downloadHits < 2; i++) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 20));
|
||||
}
|
||||
|
||||
expect(downloadHits, 2, reason: '新版本应自动重下');
|
||||
expect(m2.isActive(7, kVariantS), isTrue, reason: '自动更新应保持激活');
|
||||
expect(m2.models.first.version, 'v2.0.0',
|
||||
reason: '自动更新后立即生效新版本字节');
|
||||
expect(downloadHits, 2, reason: '新版本应自动补齐(只更新文件不激活)');
|
||||
expect(m2.isDownloaded(7, kVariantS), isTrue);
|
||||
expect(m2.isActive(7, kVariantS), isFalse,
|
||||
reason: '会话制:自动补齐不激活任何模型');
|
||||
expect(m2.models, isEmpty, reason: '新会话从仅预览开始');
|
||||
|
||||
// 手动启用后跑的是新版本
|
||||
await m2.setActive(7, kVariantS, true);
|
||||
expect(m2.models.first.version, 'v2.0.0');
|
||||
expect(m2.models.first.bytes, _modelBytes);
|
||||
});
|
||||
|
||||
@@ -189,17 +194,25 @@ void main() {
|
||||
expect(m2.errorOf(7, kVariantS), isNotNull);
|
||||
});
|
||||
|
||||
test('激活集持久化:重启后恢复激活且已下载的模型', () async {
|
||||
test('会话制:进入新会话清空激活,文件保留,再启用不重新下载', () async {
|
||||
final m = manager(client([_item()]));
|
||||
await m.refresh();
|
||||
await m.downloadModel(m.catalog.first);
|
||||
expect(m.isActive(7, kVariantS), isTrue);
|
||||
|
||||
// 同一 root 新建 manager 模拟重启
|
||||
final m2 = manager(client([_item()]));
|
||||
await m2.refresh();
|
||||
expect(m2.isActive(7, kVariantS), isTrue, reason: '激活集应持久化');
|
||||
expect(m2.models.length, 1);
|
||||
expect(downloadHits, 1, reason: '重启不应触发下载');
|
||||
// 每次进入视野页 = 新会话:清空激活与已加载模型(不恢复上次使用的模型)
|
||||
m.resetForSession();
|
||||
expect(m.isActive(7, kVariantS), isFalse,
|
||||
reason: '新会话不自动恢复上次使用的模型');
|
||||
expect(m.models, isEmpty);
|
||||
expect(m.isDownloaded(7, kVariantS), isTrue, reason: '文件保留:清单显示已下载');
|
||||
|
||||
// 手动启用:直接用本地文件,不重新下载
|
||||
final before = downloadHits;
|
||||
await m.setActive(7, kVariantS, true);
|
||||
expect(m.isActive(7, kVariantS), isTrue);
|
||||
expect(m.models.length, 1);
|
||||
expect(downloadHits, before, reason: '已下载启用不应重新下载');
|
||||
});
|
||||
|
||||
test('目录下线:清理本地并移除激活', () async {
|
||||
@@ -272,24 +285,24 @@ void main() {
|
||||
reason: '取消后 .part 残留应被清理');
|
||||
});
|
||||
|
||||
test('双档位:n 档独立目录 models/7/n/,非当前档下载不加载', () async {
|
||||
test('双档位:n 档独立目录 models/7/n/,同物种只运行目标档', () async {
|
||||
final m = manager(client(
|
||||
[_item(), _item(variant: kVariantN, name: '数据集A', version: 'v1.0.0')]));
|
||||
await m.refresh();
|
||||
expect(m.catalog.length, 2);
|
||||
|
||||
// 默认 s 档:下载 s 自动激活并加载
|
||||
// 默认目标档 s:下载 s 自动激活并加载
|
||||
await m.downloadModel(m.catalog.first); // (7, s)
|
||||
expect(m.models.length, 1);
|
||||
expect(m.models.first.variant, kVariantS);
|
||||
|
||||
// 非当前档(n)下载:自动备好(激活保留),但 s 档加载列表不变
|
||||
// 同物种已有 s 在使用时下载 n:备好不启用(同物种一次只运行一档)
|
||||
final nItem = m.catalog.last;
|
||||
expect(nItem.variant, kVariantN);
|
||||
await m.downloadModel(nItem);
|
||||
expect(m.isDownloaded(7, kVariantN), isTrue);
|
||||
expect(m.isActive(7, kVariantN), isTrue, reason: '下载完成应自动备好');
|
||||
expect(m.models.length, 1, reason: '非当前档模型不应进入加载列表');
|
||||
expect(m.isActive(7, kVariantN), isFalse, reason: '同物种运行中不叠加启用');
|
||||
expect(m.models.length, 1, reason: '备好的 n 档不进加载列表');
|
||||
expect(m.models.first.variant, kVariantS);
|
||||
|
||||
// n 档文件在独立子目录(目录键 = 档位),s 档仍为同级文件
|
||||
@@ -298,38 +311,108 @@ void main() {
|
||||
expect(await File('${root.path}/7/model.tflite').exists(), isTrue,
|
||||
reason: 's 档 legacy 同级布局保持不变');
|
||||
|
||||
// 切档:热加载 n 档已备模型,s 档不再加载(无需重新下载)
|
||||
// 切目标档:只改偏好,不切换运行中的模型
|
||||
final before = downloadHits;
|
||||
await m.setMode(kVariantN);
|
||||
expect(m.mode, kVariantN);
|
||||
expect(downloadHits, before, reason: '切档不应触发下载');
|
||||
expect(downloadHits, before, reason: '切目标档不应触发下载');
|
||||
expect(m.isActive(7, kVariantS), isTrue, reason: '切档不改运行状态');
|
||||
expect(m.models.length, 1);
|
||||
expect(m.models.first.variant, kVariantS);
|
||||
|
||||
// 改用 n:同物种 s 自动停用,n 启用(无需重新下载)
|
||||
await m.setActive(7, kVariantN, true);
|
||||
expect(m.isActive(7, kVariantS), isFalse, reason: '改用自动停用另一档');
|
||||
expect(m.isActive(7, kVariantN), isTrue);
|
||||
expect(m.models.length, 1);
|
||||
expect(m.models.first.variant, kVariantN);
|
||||
expect(m.models.first.datasetName, '数据集A');
|
||||
|
||||
// 切回 s 档恢复 s 模型
|
||||
await m.setMode(kVariantS);
|
||||
await m.setActive(7, kVariantS, true);
|
||||
expect(m.models.length, 1);
|
||||
expect(m.models.first.variant, kVariantS);
|
||||
expect(m.isActive(7, kVariantN), isFalse);
|
||||
});
|
||||
|
||||
test('识别档位持久化:重启后保持档位并加载该档已激活模型', () async {
|
||||
test('识别目标档位偏好持久化;激活集为会话态不跨重启', () async {
|
||||
final m = manager(client(
|
||||
[_item(), _item(variant: kVariantN, name: '数据集A', version: 'v1.0.0')]));
|
||||
await m.refresh();
|
||||
await m.downloadModel(m.catalog.first); // (7, s)
|
||||
await m.downloadModel(m.catalog.last); // (7, n) 备好
|
||||
await m.downloadModel(m.catalog.first); // (7, s) 下载自动激活
|
||||
await m.downloadModel(m.catalog.last); // (7, n) 备好不启用
|
||||
await m.setMode(kVariantN);
|
||||
|
||||
// 同一 root 新建 manager 模拟重启:档位与激活集都持久化
|
||||
// 同一 root 新建 manager 模拟重启:目标档偏好持久化,激活集不恢复
|
||||
final m2 = manager(client(
|
||||
[_item(), _item(variant: kVariantN, name: '数据集A', version: 'v1.0.0')]));
|
||||
await m2.refresh();
|
||||
expect(m2.mode, kVariantN, reason: '识别档位应持久化');
|
||||
expect(m2.isActive(7, kVariantS), isTrue, reason: '激活集应持久化');
|
||||
expect(m2.models.length, 1);
|
||||
expect(m2.models.first.variant, kVariantN, reason: '重启后加载当前档位模型');
|
||||
expect(downloadHits, 2, reason: '重启与切档不应触发下载');
|
||||
expect(m2.mode, kVariantN, reason: '识别目标档位是用户偏好,应持久化');
|
||||
expect(m2.isActive(7, kVariantS), isFalse, reason: '激活集不跨会话恢复');
|
||||
expect(m2.isActive(7, kVariantN), isFalse);
|
||||
expect(m2.models, isEmpty, reason: '重启/新会话从仅预览开始');
|
||||
expect(m2.isDownloaded(7, kVariantS), isTrue, reason: '文件保留可即点即用');
|
||||
expect(m2.isDownloaded(7, kVariantN), isTrue);
|
||||
expect(downloadHits, 2, reason: '重启不应触发下载');
|
||||
});
|
||||
|
||||
test('多物种混合档位:改用一物种不影响其它物种的档位', () async {
|
||||
final m = manager(client([
|
||||
_item(datasetId: 7, name: '数据集A'),
|
||||
_item(datasetId: 7, variant: kVariantN, name: '数据集A'),
|
||||
_item(datasetId: 8, name: '数据集B'),
|
||||
_item(datasetId: 8, variant: kVariantN, name: '数据集B'),
|
||||
]));
|
||||
await m.refresh();
|
||||
for (final it in m.catalog) {
|
||||
await m.downloadModel(it);
|
||||
}
|
||||
expect(m.models.length, 2);
|
||||
expect(m.isActive(7, kVariantS), isTrue);
|
||||
expect(m.isActive(8, kVariantS), isTrue);
|
||||
|
||||
// 只把 7 改到 n:8 保持 s —— 两物种不同档位并行运行
|
||||
await m.setActive(7, kVariantN, true);
|
||||
expect(m.models.length, 2);
|
||||
final byId = {for (final b in m.models) b.datasetId: b.variant};
|
||||
expect(byId, {7: kVariantN, 8: kVariantS});
|
||||
expect(m.isActive(7, kVariantS), isFalse);
|
||||
expect(m.isActive(8, kVariantS), isTrue);
|
||||
expect(m.isActive(8, kVariantN), isFalse);
|
||||
|
||||
// 7 改回 s
|
||||
await m.setActive(7, kVariantS, true);
|
||||
expect(m.models.length, 2);
|
||||
expect(m.models.firstWhere((b) => b.datasetId == 7).variant, kVariantS);
|
||||
});
|
||||
|
||||
test('目标档决定首次下载自动启用:非目标档先落地不启用,目标档落地启用', () async {
|
||||
final m = manager(client(
|
||||
[_item(), _item(variant: kVariantN, name: '数据集A', version: 'v1.0.0')]));
|
||||
await m.refresh();
|
||||
await m.setMode(kVariantN); // 目标高性能
|
||||
|
||||
// 先下 s(非目标档):数据集尚无档位在使用,但不应启用 s
|
||||
await m.downloadModel(m.catalog.first);
|
||||
expect(m.isDownloaded(7, kVariantS), isTrue);
|
||||
expect(m.isActive(7, kVariantS), isFalse, reason: '非目标档落地不自动启用');
|
||||
expect(m.models, isEmpty);
|
||||
|
||||
// 目标档 n 落地:自动启用(s 保持备好)
|
||||
await m.downloadModel(m.catalog.last);
|
||||
expect(m.isActive(7, kVariantN), isTrue);
|
||||
expect(m.isActive(7, kVariantS), isFalse);
|
||||
expect(m.models.length, 1);
|
||||
expect(m.models.first.variant, kVariantN);
|
||||
});
|
||||
|
||||
test('单档目录(无目标档条目):下载即自动启用仅有的档', () async {
|
||||
final m = manager(client([_item()])); // 目录只有 s
|
||||
await m.refresh();
|
||||
await m.setMode(kVariantN); // 目标高性能但目录没有 n
|
||||
await m.downloadModel(m.catalog.first);
|
||||
expect(m.isActive(7, kVariantS), isTrue, reason: '无目标档时启用仅有的档');
|
||||
expect(m.models.length, 1);
|
||||
});
|
||||
|
||||
test('数据集仍在但 n 档下线:清理 n 档子目录,保留数据集目录', () async {
|
||||
@@ -345,4 +428,70 @@ void main() {
|
||||
expect(await Directory('${root.path}/7').exists(), isTrue,
|
||||
reason: '数据集仍在(s 档):不应删整目录');
|
||||
});
|
||||
|
||||
test('目录缓存:断网重启后展示上次目录、标出已下载、保留文件且不恢复激活', () async {
|
||||
final m = manager(client([_item()]));
|
||||
await m.refresh();
|
||||
await m.downloadModel(m.catalog.first); // (7, s) 下载并激活
|
||||
expect(m.isDownloaded(7, kVariantS), isTrue);
|
||||
|
||||
// 模拟重启 + 断网:同 root 新实例、目录接口失败
|
||||
final offline =
|
||||
manager(MockClient((_) async => http.Response('boom', 500)));
|
||||
await offline.refresh();
|
||||
|
||||
expect(offline.catalog.length, 1, reason: '无网络也应以缓存目录展示模型清单');
|
||||
expect(offline.catalog.first.datasetId, 7);
|
||||
expect(offline.error, isNotNull, reason: '未拉取成功过仍提示目录刷新失败');
|
||||
expect(offline.ready, isFalse);
|
||||
expect(offline.isDownloaded(7, kVariantS), isTrue,
|
||||
reason: '缓存目录同样扫描本地文件:按钮不误导为未下载');
|
||||
expect(offline.isActive(7, kVariantS), isFalse, reason: '会话制:不恢复激活');
|
||||
expect(offline.models, isEmpty);
|
||||
expect(await Directory('${root.path}/7').exists(), isTrue,
|
||||
reason: '离线降级(非权威目录)不得清理本地模型');
|
||||
});
|
||||
|
||||
test('目录缓存:网络成功覆盖缓存,再离线读到最近一次目录', () async {
|
||||
final m = manager(client([_item(datasetId: 7, name: '数据集A')]));
|
||||
await m.refresh(); // 缓存 v1:只有数据集 7 的 s 档
|
||||
|
||||
final m2 = manager(client([
|
||||
_item(datasetId: 7, name: '数据集A'),
|
||||
_item(datasetId: 7, variant: kVariantN, name: '数据集A'),
|
||||
_item(datasetId: 8, name: '数据集B'),
|
||||
]));
|
||||
await m2.refresh();
|
||||
expect(m2.catalog.length, 3);
|
||||
|
||||
final offline =
|
||||
manager(MockClient((_) async => http.Response('boom', 500)));
|
||||
await offline.refresh();
|
||||
expect(offline.catalog.length, 3, reason: '缓存应为最近一次成功拉取的目录');
|
||||
expect(offline.catalog.map((c) => c.datasetId).toSet(), {7, 8});
|
||||
expect(
|
||||
offline.catalog
|
||||
.where((c) => c.datasetId == 7)
|
||||
.map((c) => c.variant)
|
||||
.toSet(),
|
||||
{kVariantS, kVariantN});
|
||||
});
|
||||
|
||||
test('升级后断网首启(无缓存文件):拉取失败不执行任何清理', () async {
|
||||
// 旧版本下载的文件直接落盘(无 catalog.json):升级首启即断网的最坏情况
|
||||
final dir = Directory('${root.path}/9');
|
||||
await dir.create(recursive: true);
|
||||
await File('${dir.path}/model.tflite').writeAsBytes(_modelBytes);
|
||||
await File('${dir.path}/meta.json')
|
||||
.writeAsString(jsonEncode({'version': 'v1.0.0'}));
|
||||
|
||||
final offline =
|
||||
manager(MockClient((_) async => http.Response('boom', 500)));
|
||||
await offline.refresh();
|
||||
|
||||
expect(offline.error, isNotNull);
|
||||
expect(offline.catalog, isEmpty, reason: '无缓存可兜底:无可展示目录');
|
||||
expect(await dir.exists(), isTrue,
|
||||
reason: '清理只在网络拉取成功后执行,离线首启不得误删已下载模型');
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user