content: # test running a filesystem to exhaustion
  [cases.test_exhaustion_normal]
  defines.ERASE_CYCLES = 10
  defines.ERASE_COUNT = 256 # small bd so test runs faster
  defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
  defines.BADBLOCK_BEHAVIOR = [
      'LFS_EMUBD_BADBLOCK_PROGERROR',
      'LFS_EMUBD_BADBLOCK_ERASEERROR',
      'LFS_EMUBD_BADBLOCK_READERROR',
      'LFS_EMUBD_BADBLOCK_PROGNOOP',
      'LFS_EMUBD_BADBLOCK_ERASENOOP',
  ]
  defines.FILES = 10
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
      lfs_mount(&lfs, cfg) => 0;
      lfs_mkdir(&lfs, "roadrunner") => 0;
      lfs_unmount(&lfs) => 0;
  
      uint32_t cycle = 0;
      while (true) {
          lfs_mount(&lfs, cfg) => 0;
          for (uint32_t i = 0; i < FILES; i++) {
              // chose name, roughly random seed, and random 2^n size
              char path[1024];
              sprintf(path, "roadrunner/test%d", i);
              uint32_t prng = cycle * i;
              lfs_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2);
  
              lfs_file_t file;
              lfs_file_open(&lfs, &file, path,
                      LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
  
              for (lfs_size_t j = 0; j < size; j++) {
                  char c = 'a' + (TEST_PRNG(&prng) % 26);
                  lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                  assert(res == 1 || res == LFS_ERR_NOSPC);
                  if (res == LFS_ERR_NOSPC) {
                      int err = lfs_file_close(&lfs, &file);
                      assert(err == 0 || err == LFS_ERR_NOSPC);
                      lfs_unmount(&lfs) => 0;
                      goto exhausted;
                  }
              }
  
              int err = lfs_file_close(&lfs, &file);
              assert(err == 0 || err == LFS_ERR_NOSPC);
              if (err == LFS_ERR_NOSPC) {
                  lfs_unmount(&lfs) => 0;
                  goto exhausted;
              }
          }
  
          for (uint32_t i = 0; i < FILES; i++) {
              // check for errors
              char path[1024];
              sprintf(path, "roadrunner/test%d", i);
              uint32_t prng = cycle * i;
              lfs_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2);
  
              lfs_file_t file;
              lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
              for (lfs_size_t j = 0; j < size; j++) {
                  char c = 'a' + (TEST_PRNG(&prng) % 26);
                  char r;
                  lfs_file_read(&lfs, &file, &r, 1) => 1;
                  assert(r == c);
              }
  
              lfs_file_close(&lfs, &file) => 0;
          }
          lfs_unmount(&lfs) => 0;
  
          cycle += 1;
      }
  
  exhausted:
      // should still be readable
      lfs_mount(&lfs, cfg) => 0;
      for (uint32_t i = 0; i < FILES; i++) {
          // check for errors
          char path[1024];
          sprintf(path, "roadrunner/test%d", i);
          struct lfs_info info;
          lfs_stat(&lfs, path, &info) => 0;
      }
      lfs_unmount(&lfs) => 0;
  
      LFS_WARN("completed %d cycles", cycle);
  '''
  
  # test running a filesystem to exhaustion
  # which also requires expanding superblocks
  [cases.test_exhaustion_superblocks]
  defines.ERASE_CYCLES = 10
  defines.ERASE_COUNT = 256 # small bd so test runs faster
  defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
  defines.BADBLOCK_BEHAVIOR = [
      'LFS_EMUBD_BADBLOCK_PROGERROR',
      'LFS_EMUBD_BADBLOCK_ERASEERROR',
      'LFS_EMUBD_BADBLOCK_READERROR',
      'LFS_EMUBD_BADBLOCK_PROGNOOP',
      'LFS_EMUBD_BADBLOCK_ERASENOOP',
  ]
  defines.FILES = 10
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
  
      uint32_t cycle = 0;
      while (true) {
          lfs_mount(&lfs, cfg) => 0;
          for (uint32_t i = 0; i < FILES; i++) {
              // chose name, roughly random seed, and random 2^n size
              char path[1024];
              sprintf(path, "test%d", i);
              uint32_t prng = cycle * i;
              lfs_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2);
  
              lfs_file_t file;
              lfs_file_open(&lfs, &file, path,
                      LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
  
              for (lfs_size_t j = 0; j < size; j++) {
                  char c = 'a' + (TEST_PRNG(&prng) % 26);
                  lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                  assert(res == 1 || res == LFS_ERR_NOSPC);
                  if (res == LFS_ERR_NOSPC) {
                      int err = lfs_file_close(&lfs, &file);
                      assert(err == 0 || err == LFS_ERR_NOSPC);
                      lfs_unmount(&lfs) => 0;
                      goto exhausted;
                  }
              }
  
              int err = lfs_file_close(&lfs, &file);
              assert(err == 0 || err == LFS_ERR_NOSPC);
              if (err == LFS_ERR_NOSPC) {
                  lfs_unmount(&lfs) => 0;
                  goto exhausted;
              }
          }
  
          for (uint32_t i = 0; i < FILES; i++) {
              // check for errors
              char path[1024];
              sprintf(path, "test%d", i);
              uint32_t prng = cycle * i;
              lfs_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2);
  
              lfs_file_t file;
              lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
              for (lfs_size_t j = 0; j < size; j++) {
                  char c = 'a' + (TEST_PRNG(&prng) % 26);
                  char r;
                  lfs_file_read(&lfs, &file, &r, 1) => 1;
                  assert(r == c);
              }
  
              lfs_file_close(&lfs, &file) => 0;
          }
          lfs_unmount(&lfs) => 0;
  
          cycle += 1;
      }
  
  exhausted:
      // should still be readable
      lfs_mount(&lfs, cfg) => 0;
      for (uint32_t i = 0; i < FILES; i++) {
          // check for errors
          char path[1024];
          struct lfs_info info;
          sprintf(path, "test%d", i);
          lfs_stat(&lfs, path, &info) => 0;
      }
      lfs_unmount(&lfs) => 0;
  
      LFS_WARN("completed %d cycles", cycle);
  '''
  
  # These are a sort of high-level litmus test for wear-leveling. One definition
  # of wear-leveling is that increasing a block device's space translates directly
  # into increasing the block devices lifetime. This is something we can actually
  # check for.
  
  # wear-level test running a filesystem to exhaustion
  [cases.test_exhuastion_wear_leveling]
  defines.ERASE_CYCLES = 20
  defines.ERASE_COUNT = 256 # small bd so test runs faster
  defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
  defines.FILES = 10
  code = '''
      uint32_t run_cycles[2];
      const uint32_t run_block_count[2] = {BLOCK_COUNT/2, BLOCK_COUNT};
  
      for (int run = 0; run < 2; run++) {
          for (lfs_block_t b = 0; b < BLOCK_COUNT; b++) {
              lfs_emubd_setwear(cfg, b,
                      (b < run_block_count[run]) ? 0 : ERASE_CYCLES) => 0;
          }
  
          lfs_t lfs;
          lfs_format(&lfs, cfg) => 0;
          lfs_mount(&lfs, cfg) => 0;
          lfs_mkdir(&lfs, "roadrunner") => 0;
          lfs_unmount(&lfs) => 0;
  
          uint32_t cycle = 0;
          while (true) {
              lfs_mount(&lfs, cfg) => 0;
              for (uint32_t i = 0; i < FILES; i++) {
                  // chose name, roughly random seed, and random 2^n size
                  char path[1024];
                  sprintf(path, "roadrunner/test%d", i);
                  uint32_t prng = cycle * i;
                  lfs_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2);
  
                  lfs_file_t file;
                  lfs_file_open(&lfs, &file, path,
                          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
  
                  for (lfs_size_t j = 0; j < size; j++) {
                      char c = 'a' + (TEST_PRNG(&prng) % 26);
                      lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                      assert(res == 1 || res == LFS_ERR_NOSPC);
                      if (res == LFS_ERR_NOSPC) {
                          int err = lfs_file_close(&lfs, &file);
                          assert(err == 0 || err == LFS_ERR_NOSPC);
                          lfs_unmount(&lfs) => 0;
                          goto exhausted;
                      }
                  }
  
                  int err = lfs_file_close(&lfs, &file);
                  assert(err == 0 || err == LFS_ERR_NOSPC);
                  if (err == LFS_ERR_NOSPC) {
                      lfs_unmount(&lfs) => 0;
                      goto exhausted;
                  }
              }
  
              for (uint32_t i = 0; i < FILES; i++) {
                  // check for errors
                  char path[1024];
                  sprintf(path, "roadrunner/test%d", i);
                  uint32_t prng = cycle * i;
                  lfs_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2);
  
                  lfs_file_t file;
                  lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
                  for (lfs_size_t j = 0; j < size; j++) {
                      char c = 'a' + (TEST_PRNG(&prng) % 26);
                      char r;
                      lfs_file_read(&lfs, &file, &r, 1) => 1;
                      assert(r == c);
                  }
  
                  lfs_file_close(&lfs, &file) => 0;
              }
              lfs_unmount(&lfs) => 0;
  
              cycle += 1;
          }
  
  exhausted:
          // should still be readable
          lfs_mount(&lfs, cfg) => 0;
          for (uint32_t i = 0; i < FILES; i++) {
              // check for errors
              char path[1024];
              struct lfs_info info;
              sprintf(path, "roadrunner/test%d", i);
              lfs_stat(&lfs, path, &info) => 0;
          }
          lfs_unmount(&lfs) => 0;
  
          run_cycles[run] = cycle;
          LFS_WARN("completed %d blocks %d cycles",
                  run_block_count[run], run_cycles[run]);
      }
  
      // check we increased the lifetime by 2x with ~10% error
      LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
  '''
  
  # wear-level test + expanding superblock
  [cases.test_exhaustion_wear_leveling_superblocks]
  defines.ERASE_CYCLES = 20
  defines.ERASE_COUNT = 256 # small bd so test runs faster
  defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
  defines.FILES = 10
  code = '''
      uint32_t run_cycles[2];
      const uint32_t run_block_count[2] = {BLOCK_COUNT/2, BLOCK_COUNT};
  
      for (int run = 0; run < 2; run++) {
          for (lfs_block_t b = 0; b < BLOCK_COUNT; b++) {
              lfs_emubd_setwear(cfg, b,
                      (b < run_block_count[run]) ? 0 : ERASE_CYCLES) => 0;
          }
  
          lfs_t lfs;
          lfs_format(&lfs, cfg) => 0;
  
          uint32_t cycle = 0;
          while (true) {
              lfs_mount(&lfs, cfg) => 0;
              for (uint32_t i = 0; i < FILES; i++) {
                  // chose name, roughly random seed, and random 2^n size
                  char path[1024];
                  sprintf(path, "test%d", i);
                  uint32_t prng = cycle * i;
                  lfs_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2);
  
                  lfs_file_t file;
                  lfs_file_open(&lfs, &file, path,
                          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
  
                  for (lfs_size_t j = 0; j < size; j++) {
                      char c = 'a' + (TEST_PRNG(&prng) % 26);
                      lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                      assert(res == 1 || res == LFS_ERR_NOSPC);
                      if (res == LFS_ERR_NOSPC) {
                          int err = lfs_file_close(&lfs, &file);
                          assert(err == 0 || err == LFS_ERR_NOSPC);
                          lfs_unmount(&lfs) => 0;
                          goto exhausted;
                      }
                  }
  
                  int err = lfs_file_close(&lfs, &file);
                  assert(err == 0 || err == LFS_ERR_NOSPC);
                  if (err == LFS_ERR_NOSPC) {
                      lfs_unmount(&lfs) => 0;
                      goto exhausted;
                  }
              }
  
              for (uint32_t i = 0; i < FILES; i++) {
                  // check for errors
                  char path[1024];
                  sprintf(path, "test%d", i);
                  uint32_t prng = cycle * i;
                  lfs_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2);
  
                  lfs_file_t file;
                  lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
                  for (lfs_size_t j = 0; j < size; j++) {
                      char c = 'a' + (TEST_PRNG(&prng) % 26);
                      char r;
                      lfs_file_read(&lfs, &file, &r, 1) => 1;
                      assert(r == c);
                  }
  
                  lfs_file_close(&lfs, &file) => 0;
              }
              lfs_unmount(&lfs) => 0;
  
              cycle += 1;
          }
  
  exhausted:
          // should still be readable
          lfs_mount(&lfs, cfg) => 0;
          for (uint32_t i = 0; i < FILES; i++) {
              // check for errors
              char path[1024];
              struct lfs_info info;
              sprintf(path, "test%d", i);
              lfs_stat(&lfs, path, &info) => 0;
          }
          lfs_unmount(&lfs) => 0;
  
          run_cycles[run] = cycle;
          LFS_WARN("completed %d blocks %d cycles",
                  run_block_count[run], run_cycles[run]);
      }
  
      // check we increased the lifetime by 2x with ~10% error
      LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
  '''
  
  # test that we wear blocks roughly evenly
  [cases.test_exhaustion_wear_distribution]
  defines.ERASE_CYCLES = 0xffffffff
  defines.ERASE_COUNT = 256 # small bd so test runs faster
  defines.BLOCK_CYCLES = [5, 4, 3, 2, 1]
  defines.CYCLES = 100
  defines.FILES = 10
  if = 'BLOCK_CYCLES < CYCLES/10'
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
      lfs_mount(&lfs, cfg) => 0;
      lfs_mkdir(&lfs, "roadrunner") => 0;
      lfs_unmount(&lfs) => 0;
  
      uint32_t cycle = 0;
      while (cycle < CYCLES) {
          lfs_mount(&lfs, cfg) => 0;
          for (uint32_t i = 0; i < FILES; i++) {
              // chose name, roughly random seed, and random 2^n size
              char path[1024];
              sprintf(path, "roadrunner/test%d", i);
              uint32_t prng = cycle * i;
              lfs_size_t size = 1 << 4; //((TEST_PRNG(&prng) % 10)+2);
  
              lfs_file_t file;
              lfs_file_open(&lfs, &file, path,
                      LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
  
              for (lfs_size_t j = 0; j < size; j++) {
                  char c = 'a' + (TEST_PRNG(&prng) % 26);
                  lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
                  assert(res == 1 || res == LFS_ERR_NOSPC);
                  if (res == LFS_ERR_NOSPC) {
                      int err = lfs_file_close(&lfs, &file);
                      assert(err == 0 || err == LFS_ERR_NOSPC);
                      lfs_unmount(&lfs) => 0;
                      goto exhausted;
                  }
              }
  
              int err = lfs_file_close(&lfs, &file);
              assert(err == 0 || err == LFS_ERR_NOSPC);
              if (err == LFS_ERR_NOSPC) {
                  lfs_unmount(&lfs) => 0;
                  goto exhausted;
              }
          }
  
          for (uint32_t i = 0; i < FILES; i++) {
              // check for errors
              char path[1024];
              sprintf(path, "roadrunner/test%d", i);
              uint32_t prng = cycle * i;
              lfs_size_t size = 1 << 4; //((TEST_PRNG(&prng) % 10)+2);
  
              lfs_file_t file;
              lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
              for (lfs_size_t j = 0; j < size; j++) {
                  char c = 'a' + (TEST_PRNG(&prng) % 26);
                  char r;
                  lfs_file_read(&lfs, &file, &r, 1) => 1;
                  assert(r == c);
              }
  
              lfs_file_close(&lfs, &file) => 0;
          }
          lfs_unmount(&lfs) => 0;
  
          cycle += 1;
      }
  
  exhausted:
      // should still be readable
      lfs_mount(&lfs, cfg) => 0;
      for (uint32_t i = 0; i < FILES; i++) {
          // check for errors
          char path[1024];
          struct lfs_info info;
          sprintf(path, "roadrunner/test%d", i);
          lfs_stat(&lfs, path, &info) => 0;
      }
      lfs_unmount(&lfs) => 0;
  
      LFS_WARN("completed %d cycles", cycle);
  
      // check the wear on our block device
      lfs_emubd_wear_t minwear = -1;
      lfs_emubd_wear_t totalwear = 0;
      lfs_emubd_wear_t maxwear = 0;
      // skip 0 and 1 as superblock movement is intentionally avoided
      for (lfs_block_t b = 2; b < BLOCK_COUNT; b++) {
          lfs_emubd_wear_t wear = lfs_emubd_wear(cfg, b);
          printf("%08x: wear %d\n", b, wear);
          assert(wear >= 0);
          if (wear < minwear) {
              minwear = wear;
          }
          if (wear > maxwear) {
              maxwear = wear;
          }
          totalwear += wear;
      }
      lfs_emubd_wear_t avgwear = totalwear / BLOCK_COUNT;
      LFS_WARN("max wear: %d cycles", maxwear);
      LFS_WARN("avg wear: %d cycles", totalwear / (int)BLOCK_COUNT);
      LFS_WARN("min wear: %d cycles", minwear);
  
      // find standard deviation^2
      lfs_emubd_wear_t dev2 = 0;
      for (lfs_block_t b = 2; b < BLOCK_COUNT; b++) {
          lfs_emubd_wear_t wear = lfs_emubd_wear(cfg, b);
          assert(wear >= 0);
          lfs_emubd_swear_t diff = wear - avgwear;
          dev2 += diff*diff;
      }
      dev2 /= totalwear;
      LFS_WARN("std dev^2: %d", dev2);
      assert(dev2 < 8);
  '''
  
  