queueOrder.test.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. /**
  2. * The comparator every "queue order" surface shares (#3043).
  3. *
  4. * It has to agree with the scheduler's ORDER BY in
  5. * `print_scheduler.check_queue`, because a timeline or a pending list that
  6. * disagrees is telling the user the queue will run in an order it won't.
  7. */
  8. import { describe, it, expect } from 'vitest';
  9. import { compareQueueOrder, queueLaneKey } from '../../utils/queueOrder';
  10. interface Item {
  11. id: number;
  12. printer_id?: number | null;
  13. target_model?: string | null;
  14. been_jumped?: boolean;
  15. print_time_seconds?: number | null;
  16. position: number;
  17. }
  18. const item = (id: number, over: Partial<Item> = {}): Item => ({
  19. id,
  20. printer_id: 1,
  21. position: id,
  22. print_time_seconds: 3600,
  23. ...over,
  24. });
  25. const idsInOrder = (items: Item[], sjf: boolean) =>
  26. [...items].sort((a, b) => compareQueueOrder(a, b, sjf)).map(i => i.id);
  27. describe('compareQueueOrder', () => {
  28. it('leaves the queue in position order when SJF is off', () => {
  29. const items = [
  30. item(1, { position: 3, print_time_seconds: 60 }),
  31. item(2, { position: 1, print_time_seconds: 99999 }),
  32. item(3, { position: 2, print_time_seconds: 600 }),
  33. ];
  34. expect(idsInOrder(items, false)).toEqual([2, 3, 1]);
  35. });
  36. it('puts the shortest print first when SJF is on', () => {
  37. const items = [
  38. item(1, { position: 1, print_time_seconds: 7200 }),
  39. item(2, { position: 2, print_time_seconds: 600 }),
  40. item(3, { position: 3, print_time_seconds: 3600 }),
  41. ];
  42. expect(idsInOrder(items, true)).toEqual([2, 3, 1]);
  43. });
  44. it('sorts an item with no known duration last, not first', () => {
  45. // Treating a missing duration as 0 would make an unsliced job win every
  46. // comparison it entered -- the SQL says NULLS LAST for the same reason.
  47. const items = [
  48. item(1, { position: 1, print_time_seconds: null }),
  49. item(2, { position: 2, print_time_seconds: 7200 }),
  50. item(3, { position: 3, print_time_seconds: undefined }),
  51. ];
  52. expect(idsInOrder(items, true)).toEqual([2, 1, 3]);
  53. });
  54. it('promotes a jumped item ahead of a shorter one', () => {
  55. // Starvation guard: a long print that keeps being overtaken has to run
  56. // eventually, so once it has been jumped it outranks print time.
  57. const items = [
  58. item(1, { position: 1, print_time_seconds: 36000, been_jumped: true }),
  59. item(2, { position: 2, print_time_seconds: 300 }),
  60. ];
  61. expect(idsInOrder(items, true)).toEqual([1, 2]);
  62. });
  63. it('falls back to position when two prints are the same length', () => {
  64. const items = [
  65. item(1, { position: 5, print_time_seconds: 3600 }),
  66. item(2, { position: 2, print_time_seconds: 3600 }),
  67. ];
  68. expect(idsInOrder(items, true)).toEqual([2, 1]);
  69. });
  70. });
  71. describe('queueLaneKey', () => {
  72. it('names a lane after the printer, the model, or neither', () => {
  73. expect(queueLaneKey(item(1, { printer_id: 4 }))).toBe('printer:4');
  74. expect(queueLaneKey(item(2, { printer_id: null, target_model: 'X1C' }))).toBe('model:X1C');
  75. expect(queueLaneKey(item(3, { printer_id: null, target_model: null }))).toBe('unassigned');
  76. });
  77. it('keeps two models apart that share a first letter', () => {
  78. // The flat pending list used to fold the model down to -charCodeAt(0),
  79. // which gave X1C and X2D the same key and interleaved the two lanes.
  80. expect(queueLaneKey(item(1, { printer_id: null, target_model: 'X1C' }))).not.toBe(
  81. queueLaneKey(item(2, { printer_id: null, target_model: 'X2D' })),
  82. );
  83. });
  84. });
  85. describe('compareQueueOrder across printers and models (#3200)', () => {
  86. it('orders a pinned job and an Any-model job by position, not by lane', () => {
  87. const items = [
  88. item(1, { printer_id: null, target_model: 'P2S', position: 2 }),
  89. item(2, { printer_id: null, target_model: 'P2S', position: 3 }),
  90. item(3, { printer_id: 1, position: 1 }),
  91. ];
  92. expect(idsInOrder(items, false)).toEqual([3, 1, 2]);
  93. });
  94. it('puts an Any-model job above a pinned one when it is higher in the queue', () => {
  95. const items = [
  96. item(1, { printer_id: 1, position: 2 }),
  97. item(2, { printer_id: null, target_model: 'P2S', position: 1 }),
  98. ];
  99. expect(idsInOrder(items, false)).toEqual([2, 1]);
  100. });
  101. it('applies Shortest-Job-First across lanes', () => {
  102. const items = [
  103. item(1, { printer_id: 2, position: 1, print_time_seconds: 7200 }),
  104. item(2, { printer_id: 1, position: 2, print_time_seconds: 7200 }),
  105. item(3, { printer_id: null, target_model: 'X1C', position: 3, print_time_seconds: 600 }),
  106. item(4, { printer_id: 1, position: 4, print_time_seconds: 300 }),
  107. ];
  108. expect(idsInOrder(items, true)).toEqual([4, 3, 1, 2]);
  109. });
  110. it('breaks a position tie by id, like the scheduler', () => {
  111. const items = [
  112. item(7, { printer_id: null, target_model: 'P2S', position: 1 }),
  113. item(5, { printer_id: 1, position: 1 }),
  114. ];
  115. expect(idsInOrder(items, false)).toEqual([5, 7]);
  116. });
  117. });