content: # simple formatting test
  [cases.test_superblocks_format]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
  '''
  
  # mount/unmount
  [cases.test_superblocks_mount]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
      lfs_mount(&lfs, cfg) => 0;
      lfs_unmount(&lfs) => 0;
  '''
  
  # make sure the magic string "littlefs" is always at offset=8
  [cases.test_superblocks_magic]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
  
      // check our magic string
      //
      // note if we lose power we may not have the magic string in both blocks!
      // but we don't lose power in this test so we can assert the magic string
      // is present in both
      uint8_t magic[lfs_max(16, READ_SIZE)];
      cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
      assert(memcmp(&magic[8], "littlefs", 8) == 0);
      cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
      assert(memcmp(&magic[8], "littlefs", 8) == 0);
  '''
  
  # mount/unmount from interpretting a previous superblock block_count
  [cases.test_superblocks_mount_unknown_block_count]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
  
      memset(&lfs, 0, sizeof(lfs));
      struct lfs_config tweaked_cfg = *cfg;
      tweaked_cfg.block_count = 0;
      lfs_mount(&lfs, &tweaked_cfg) => 0;
      assert(lfs.block_count == cfg->block_count);
      lfs_unmount(&lfs) => 0;
  '''
  
  # reentrant format
  [cases.test_superblocks_reentrant_format]
  reentrant = true
  defines.POWERLOSS_BEHAVIOR = [
      'LFS_EMUBD_POWERLOSS_NOOP',
      'LFS_EMUBD_POWERLOSS_OOO',
  ]
  code = '''
      lfs_t lfs;
      int err = lfs_mount(&lfs, cfg);
      if (err) {
          lfs_format(&lfs, cfg) => 0;
          lfs_mount(&lfs, cfg) => 0;
      }
      lfs_unmount(&lfs) => 0;
  '''
  
  # invalid mount
  [cases.test_superblocks_invalid_mount]
  code = '''
      lfs_t lfs;
      lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
  '''
  
  # test we can read superblock info through lfs_fs_stat
  [cases.test_superblocks_stat]
  if = 'DISK_VERSION == 0'
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
  
      // test we can mount and read fsinfo
      lfs_mount(&lfs, cfg) => 0;
  
      struct lfs_fsinfo fsinfo;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.disk_version == LFS_DISK_VERSION);
      assert(fsinfo.name_max == LFS_NAME_MAX);
      assert(fsinfo.file_max == LFS_FILE_MAX);
      assert(fsinfo.attr_max == LFS_ATTR_MAX);
  
      lfs_unmount(&lfs) => 0;
  '''
  
  [cases.test_superblocks_stat_tweaked]
  if = 'DISK_VERSION == 0'
  defines.TWEAKED_NAME_MAX = 63
  defines.TWEAKED_FILE_MAX = '(1 << 16)-1'
  defines.TWEAKED_ATTR_MAX = 512
  code = '''
      // create filesystem with tweaked params
      struct lfs_config tweaked_cfg = *cfg;
      tweaked_cfg.name_max = TWEAKED_NAME_MAX;
      tweaked_cfg.file_max = TWEAKED_FILE_MAX;
      tweaked_cfg.attr_max = TWEAKED_ATTR_MAX;
  
      lfs_t lfs;
      lfs_format(&lfs, &tweaked_cfg) => 0;
  
      // test we can mount and read these params with the original config
      lfs_mount(&lfs, cfg) => 0;
  
      struct lfs_fsinfo fsinfo;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.disk_version == LFS_DISK_VERSION);
      assert(fsinfo.name_max == TWEAKED_NAME_MAX);
      assert(fsinfo.file_max == TWEAKED_FILE_MAX);
      assert(fsinfo.attr_max == TWEAKED_ATTR_MAX);
  
      lfs_unmount(&lfs) => 0;
  '''
  
  # expanding superblock
  [cases.test_superblocks_expand]
  defines.BLOCK_CYCLES = [32, 33, 1]
  defines.N = [10, 100, 1000]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
      lfs_mount(&lfs, cfg) => 0;
      for (int i = 0; i < N; i++) {
          lfs_file_t file;
          lfs_file_open(&lfs, &file, "dummy",
                  LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
          lfs_file_close(&lfs, &file) => 0;
          struct lfs_info info;
          lfs_stat(&lfs, "dummy", &info) => 0;
          assert(strcmp(info.name, "dummy") == 0);
          assert(info.type == LFS_TYPE_REG);
          lfs_remove(&lfs, "dummy") => 0;
      }
      lfs_unmount(&lfs) => 0;
  
      // one last check after power-cycle
      lfs_mount(&lfs, cfg) => 0;
      lfs_file_t file;
      lfs_file_open(&lfs, &file, "dummy",
              LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
      lfs_file_close(&lfs, &file) => 0;
      struct lfs_info info;
      lfs_stat(&lfs, "dummy", &info) => 0;
      assert(strcmp(info.name, "dummy") == 0);
      assert(info.type == LFS_TYPE_REG);
      lfs_unmount(&lfs) => 0;
  '''
  
  # make sure the magic string "littlefs" is always at offset=8
  [cases.test_superblocks_magic_expand]
  defines.BLOCK_CYCLES = [32, 33, 1]
  defines.N = [10, 100, 1000]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
      lfs_mount(&lfs, cfg) => 0;
      for (int i = 0; i < N; i++) {
          lfs_file_t file;
          lfs_file_open(&lfs, &file, "dummy",
                  LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
          lfs_file_close(&lfs, &file) => 0;
          struct lfs_info info;
          lfs_stat(&lfs, "dummy", &info) => 0;
          assert(strcmp(info.name, "dummy") == 0);
          assert(info.type == LFS_TYPE_REG);
          lfs_remove(&lfs, "dummy") => 0;
      }
      lfs_unmount(&lfs) => 0;
  
      // check our magic string
      //
      // note if we lose power we may not have the magic string in both blocks!
      // but we don't lose power in this test so we can assert the magic string
      // is present in both
      uint8_t magic[lfs_max(16, READ_SIZE)];
      cfg->read(cfg, 0, 0, magic, lfs_max(16, READ_SIZE)) => 0;
      assert(memcmp(&magic[8], "littlefs", 8) == 0);
      cfg->read(cfg, 1, 0, magic, lfs_max(16, READ_SIZE)) => 0;
      assert(memcmp(&magic[8], "littlefs", 8) == 0);
  '''
  
  # expanding superblock with power cycle
  [cases.test_superblocks_expand_power_cycle]
  defines.BLOCK_CYCLES = [32, 33, 1]
  defines.N = [10, 100, 1000]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
      for (int i = 0; i < N; i++) {
          lfs_mount(&lfs, cfg) => 0;
          // remove lingering dummy?
          struct lfs_info info;
          int err = lfs_stat(&lfs, "dummy", &info);
          assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
          if (!err) {
              assert(strcmp(info.name, "dummy") == 0);
              assert(info.type == LFS_TYPE_REG);
              lfs_remove(&lfs, "dummy") => 0;
          }
  
          lfs_file_t file;
          lfs_file_open(&lfs, &file, "dummy",
                  LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
          lfs_file_close(&lfs, &file) => 0;
          lfs_stat(&lfs, "dummy", &info) => 0;
          assert(strcmp(info.name, "dummy") == 0);
          assert(info.type == LFS_TYPE_REG);
          lfs_unmount(&lfs) => 0;
      }
  
      // one last check after power-cycle
      lfs_mount(&lfs, cfg) => 0;
      struct lfs_info info;
      lfs_stat(&lfs, "dummy", &info) => 0;
      assert(strcmp(info.name, "dummy") == 0);
      assert(info.type == LFS_TYPE_REG);
      lfs_unmount(&lfs) => 0;
  '''
  
  # reentrant expanding superblock
  [cases.test_superblocks_reentrant_expand]
  defines.BLOCK_CYCLES = [2, 1]
  defines.N = 24
  reentrant = true
  defines.POWERLOSS_BEHAVIOR = [
      'LFS_EMUBD_POWERLOSS_NOOP',
      'LFS_EMUBD_POWERLOSS_OOO',
  ]
  code = '''
      lfs_t lfs;
      int err = lfs_mount(&lfs, cfg);
      if (err) {
          lfs_format(&lfs, cfg) => 0;
          lfs_mount(&lfs, cfg) => 0;
      }
  
      for (int i = 0; i < N; i++) {
          // remove lingering dummy?
          struct lfs_info info;
          err = lfs_stat(&lfs, "dummy", &info);
          assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
          if (!err) {
              assert(strcmp(info.name, "dummy") == 0);
              assert(info.type == LFS_TYPE_REG);
              lfs_remove(&lfs, "dummy") => 0;
          }
  
          lfs_file_t file;
          lfs_file_open(&lfs, &file, "dummy",
                  LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
          lfs_file_close(&lfs, &file) => 0;
          lfs_stat(&lfs, "dummy", &info) => 0;
          assert(strcmp(info.name, "dummy") == 0);
          assert(info.type == LFS_TYPE_REG);
      }
  
      lfs_unmount(&lfs) => 0;
  
      // one last check after power-cycle
      lfs_mount(&lfs, cfg) => 0;
      struct lfs_info info;
      lfs_stat(&lfs, "dummy", &info) => 0;
      assert(strcmp(info.name, "dummy") == 0);
      assert(info.type == LFS_TYPE_REG);
      lfs_unmount(&lfs) => 0;
  '''
  
  
  # mount with unknown block_count
  [cases.test_superblocks_unknown_blocks]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
  
      // known block_size/block_count
      cfg->block_size = BLOCK_SIZE;
      cfg->block_count = BLOCK_COUNT;
      lfs_mount(&lfs, cfg) => 0;
      struct lfs_fsinfo fsinfo;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_unmount(&lfs) => 0;
  
      // unknown block_count
      cfg->block_size = BLOCK_SIZE;
      cfg->block_count = 0;
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_unmount(&lfs) => 0;
  
      // do some work
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_file_t file;
      lfs_file_open(&lfs, &file, "test",
              LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
      lfs_file_write(&lfs, &file, "hello!", 6) => 6;
      lfs_file_close(&lfs, &file) => 0;
      lfs_unmount(&lfs) => 0;
  
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
      uint8_t buffer[256];
      lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
      lfs_file_close(&lfs, &file) => 0;
      assert(memcmp(buffer, "hello!", 6) == 0);
      lfs_unmount(&lfs) => 0;
  '''
  
  # mount with blocks fewer than the erase_count
  [cases.test_superblocks_fewer_blocks]
  defines.BLOCK_COUNT = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2']
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
  
      // known block_size/block_count
      cfg->block_size = BLOCK_SIZE;
      cfg->block_count = BLOCK_COUNT;
      lfs_mount(&lfs, cfg) => 0;
      struct lfs_fsinfo fsinfo;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_unmount(&lfs) => 0;
  
      // incorrect block_count
      cfg->block_size = BLOCK_SIZE;
      cfg->block_count = ERASE_COUNT;
      lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
  
      // unknown block_count
      cfg->block_size = BLOCK_SIZE;
      cfg->block_count = 0;
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_unmount(&lfs) => 0;
  
      // do some work
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_file_t file;
      lfs_file_open(&lfs, &file, "test",
              LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
      lfs_file_write(&lfs, &file, "hello!", 6) => 6;
      lfs_file_close(&lfs, &file) => 0;
      lfs_unmount(&lfs) => 0;
  
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
      uint8_t buffer[256];
      lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
      lfs_file_close(&lfs, &file) => 0;
      assert(memcmp(buffer, "hello!", 6) == 0);
      lfs_unmount(&lfs) => 0;
  '''
  
  # mount with more blocks than the erase_count
  [cases.test_superblocks_more_blocks]
  defines.FORMAT_BLOCK_COUNT = '2*ERASE_COUNT'
  in = 'lfs.c'
  code = '''
      lfs_t lfs;
      lfs_init(&lfs, cfg) => 0;
      lfs.block_count = BLOCK_COUNT;
  
      lfs_mdir_t root = {
          .pair = {0, 0}, // make sure this goes into block 0
          .rev = 0,
          .off = sizeof(uint32_t),
          .etag = 0xffffffff,
          .count = 0,
          .tail = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
          .erased = false,
          .split = false,
      };
  
      lfs_superblock_t superblock = {
          .version     = LFS_DISK_VERSION,
          .block_size  = BLOCK_SIZE,
          .block_count = FORMAT_BLOCK_COUNT,
          .name_max    = LFS_NAME_MAX,
          .file_max    = LFS_FILE_MAX,
          .attr_max    = LFS_ATTR_MAX,
      };
  
      lfs_superblock_tole32(&superblock);
      lfs_dir_commit(&lfs, &root, LFS_MKATTRS(
              {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL},
              {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"},
              {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
                  &superblock})) => 0;
      lfs_deinit(&lfs) => 0;
  
      // known block_size/block_count
      cfg->block_size = BLOCK_SIZE;
      cfg->block_count = BLOCK_COUNT;
      lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
  '''
  
  # mount and grow the filesystem
  [cases.test_superblocks_grow]
  defines.BLOCK_COUNT = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2']
  defines.BLOCK_COUNT_2 = 'ERASE_COUNT'
  defines.KNOWN_BLOCK_COUNT = [true, false]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
  
      if (KNOWN_BLOCK_COUNT) {
          cfg->block_count = BLOCK_COUNT;
      } else {
          cfg->block_count = 0;
      }
  
      // mount with block_size < erase_size
      lfs_mount(&lfs, cfg) => 0;
      struct lfs_fsinfo fsinfo;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_unmount(&lfs) => 0;
  
      // same size is a noop
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_grow(&lfs, BLOCK_COUNT) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_unmount(&lfs) => 0;
  
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT);
      lfs_unmount(&lfs) => 0;
  
      // grow to new size
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT_2);
      lfs_unmount(&lfs) => 0;
  
      if (KNOWN_BLOCK_COUNT) {
          cfg->block_count = BLOCK_COUNT_2;
      } else {
          cfg->block_count = 0;
      }
  
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT_2);
      lfs_unmount(&lfs) => 0;
  
      // mounting with the previous size should fail
      cfg->block_count = BLOCK_COUNT;
      lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
  
      if (KNOWN_BLOCK_COUNT) {
          cfg->block_count = BLOCK_COUNT_2;
      } else {
          cfg->block_count = 0;
      }
  
      // same size is a noop
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT_2);
      lfs_unmount(&lfs) => 0;
  
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT_2);
      lfs_unmount(&lfs) => 0;
  
      // do some work
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT_2);
      lfs_file_t file;
      lfs_file_open(&lfs, &file, "test",
              LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
      lfs_file_write(&lfs, &file, "hello!", 6) => 6;
      lfs_file_close(&lfs, &file) => 0;
      lfs_unmount(&lfs) => 0;
  
      lfs_mount(&lfs, cfg) => 0;
      lfs_fs_stat(&lfs, &fsinfo) => 0;
      assert(fsinfo.block_size == BLOCK_SIZE);
      assert(fsinfo.block_count == BLOCK_COUNT_2);
      lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
      uint8_t buffer[256];
      lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
      lfs_file_close(&lfs, &file) => 0;
      assert(memcmp(buffer, "hello!", 6) == 0);
      lfs_unmount(&lfs) => 0;
  '''
  
  # test that metadata_max does not cause problems for superblock compaction
  [cases.test_superblocks_metadata_max]
  defines.METADATA_MAX = [
      'lfs_max(512, PROG_SIZE)',
      'lfs_max(BLOCK_SIZE/2, PROG_SIZE)',
      'BLOCK_SIZE'
  ]
  defines.N = [10, 100, 1000]
  code = '''
      lfs_t lfs;
      lfs_format(&lfs, cfg) => 0;
      lfs_mount(&lfs, cfg) => 0;
      for (int i = 0; i < N; i++) {
          lfs_file_t file;
          char name[256];
          sprintf(name, "hello%03x", i);
          lfs_file_open(&lfs, &file, name,
                  LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
          lfs_file_close(&lfs, &file) => 0;
          struct lfs_info info;
          lfs_stat(&lfs, name, &info) => 0;
          assert(strcmp(info.name, name) == 0);
          assert(info.type == LFS_TYPE_REG);
      }
      lfs_unmount(&lfs) => 0;
  '''
  