Testing disk space exceptions with CopyToAsync

By | September 1, 2014

I got an error report from the field in Upload to YouTube where the user was getting an unhandled exception when I copied their Camera Roll video to temporary storage to hand off to Movie Maker for editing. Great catch, now to shore up the UX when this happens. Only problem is, I need to test filling up the disk during a copy. How could I possibly do this?

It was suggested that I simply write to the disk until it’s full. Yeah, if you’re testing filling up a disk that’s the way to do it. But what bout filling up the disk during a copy? Hmm… well, if I wanted to do this I basically need to create a source file that’s as big as it needs to be to fill the disk.

Enter EndlessReadStream :)

If you ever need to test this kind of scenario, hopefully this will come in handy for you as well!

   1: class EndlessReadStream : Stream
   2: {
   3:     public override bool CanRead
   4:     {
   5:         get { return true; }
   6:     }
   7:
   8:     public override bool CanSeek
   9:     {
  10:         get { return true; }
  11:     }
  12:
  13:     public override bool CanWrite
  14:     {
  15:         get { return false; }
  16:     }
  17:
  18:     public override void Flush()
  19:     {
  20:         return;
  21:     }
  22:
  23:     public override long Length
  24:     {
  25:         get { return long.MaxValue; }
  26:     }
  27:
  28:     public override long Position { get; set; }
  29:
  30:     public override int Read(byte[] buffer, int offset, int count)
  31:     {
  32:         for (int i = 0; i < count; i++)
  33:         {
  34:             buffer[offset + i] = 0;
  35:         }
  36:
  37:         return count;
  38:     }
  39:
  40:     public override long Seek(long offset, SeekOrigin origin)
  41:     {
  42:         return 0;
  43:     }
  44:
  45:     public override void SetLength(long value)
  46:     {
  47:         throw new InvalidOperationException();
  48:     }
  49:
  50:     public override void Write(byte[] buffer, int offset, int count)
  51:     {
  52:         throw new InvalidOperationException();
  53:     }
  54: }

usage:

   1: using (var outStream = await outFile.OpenStreamForWriteAsync())
   2: using (var inStream = new EndlessReadStream())
   3: {
   4:     try
   5:     {
   6:         await inStream.CopyToAsync(outStream);
   7:     }
   8:     catch (Exception ex)
   9:     {
  10:         if (ex.Message.Contains("80070070"))
  11:         {
  12:             // Out of disk space!
  13:         }
  14:     }
  15: }

I do wish the API would throw an IOException in this case, but it doesn’t so we have to trap it by looking specifically for the HResult code. Curious what the Exception does look like? Here’s the output from “Copy exception detail to clipboard”:

System.Exception was caught
  HResult=-2147024784
  Message=There is not enough space on the disk. (Exception from HRESULT: 0x80070070)
  Source=mscorlib
  StackTrace:
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
       at System.IO.BufferedStream.<WriteToUnderlyingStreamAsync>d__d.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.IO.Stream.<CopyToAsyncInternal>d__2.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
       at
<My code here> 
  InnerException: