def test_int16_input(self, opset_version):
        """
        Input graph:
            func main(int16 x) {
            ....
            } -> (x)

        Before IOS17, it becomes
            func main(int32 x) {
            ....
            } -> (x)

        In IOS17+, it becomes
            func main(int32 x) {
                %cast_0: (1, 1, 1, 1, int16)(Tensor) = cast(x=%x, dtype="int16", name="cast_0")
                ....
                %cast_1: (1, 1, 1, 1, int32)(Tensor) = cast(x=%x, dtype="int32", name="cast_1")
            } -> (cast_1)
        because IOS17+ supports int16 in Runtime (but doesn't support int16 for I/O).
        """

        @mb.program(
            input_specs=[mb.TensorSpec(shape=(1, 1, 1, 1), dtype=types.int16)],
            opset_version=opset_version,
        )
        def prog(x):
            return x

        skip_type_check = opset_version is None
        prev_prog, prev_block, block = apply_pass_and_basic_check(
            prog,
            "mil_backend::adjust_io_to_supported_types",
            skip_output_type_check=True,
            skip_input_type_check=True,
        )  # output dtype id modified

        prev_inputs = list(prev_block.inputs.items())
        inputs = list(block.inputs.items())
        prev_outputs = prev_block.outputs
        outputs = block.outputs
        assert prev_inputs[0][1].dtype == types.int16
        assert prev_outputs[0].dtype == types.int16
        assert inputs[0][1].dtype == types.int32
        assert outputs[0].dtype == types.int32
        assert prev_inputs[0][1].name == inputs[0][1].name
        assert outputs[0].name == prev_outputs[0].name
        if opset_version and opset_version >= target.iOS17:
            assert get_op_types_in_program(prog) == ["cast", "cast"]
            cast_ops = [op for op in prog["main"].operations if op.op_type != "const"]
            # The first cast is for int32 to int16.
            assert cast_ops[0].x.dtype == types.int32
            assert cast_ops[0].outputs[0].dtype == types.int16
            # The second cast is for int16 to int32.
            assert cast_ops[1].x.dtype == types.int16
            assert cast_ops[1].outputs[0].dtype == types.int32
        else:
            # Before IOS17, the int16 is not supported in Runtime, so there is no cast inserted.
            assert get_op_types_in_program(prog) == []

        Input graph:
            func main(int16 x) {
            ....
            } -> (x)

        Before IOS17, it becomes
            func main(int32 x) {
            ....
            } -> (x)

        In IOS17+, it becomes
            func main(int32 x) {
                %cast_0: (1, 1, 1, 1, int16)(Tensor) = cast(x=%x, dtype="int16", name="cast_0")
                ....
                %cast_1: (1, 1, 1, 1, int32)(Tensor) = cast(x=%x, dtype="int32", name="cast_1")
            } -> (cast_1)
        because IOS17+ supports int16 in Runtime (but doesn't support int16 for I/O).
        
coremltools/converters/mil/backend/mil/passes/test_passes.py