Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
895ffc80
Unverified
Commit
895ffc80
authored
Dec 11, 2019
by
Zachary Anderson
Committed by
GitHub
Dec 11, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[flutter_tool] Handling of certain unrecoverable filesystem errors (#46617)
parent
ceab1248
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
364 additions
and
2 deletions
+364
-2
error_handling_file_system.dart
...lutter_tools/lib/src/base/error_handling_file_system.dart
+241
-0
file_system.dart
packages/flutter_tools/lib/src/base/file_system.dart
+4
-1
precache_test.dart
...ter_tools/test/commands.shard/hermetic/precache_test.dart
+1
-0
error_handling_file_system_test.dart
...t/general.shard/base/error_handling_file_system_test.dart
+99
-0
forbidden_imports_test.dart
...tter_tools/test/general.shard/forbidden_imports_test.dart
+2
-0
flutter_command_test.dart
...tools/test/general.shard/runner/flutter_command_test.dart
+12
-0
testbed_test.dart
packages/flutter_tools/test/general.shard/testbed_test.dart
+5
-1
No files found.
packages/flutter_tools/lib/src/base/error_handling_file_system.dart
0 → 100644
View file @
895ffc80
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:convert'
;
import
'dart:io'
as
io
show
Directory
,
File
,
Link
;
import
'package:file/file.dart'
;
import
'package:meta/meta.dart'
;
import
'common.dart'
show
throwToolExit
;
import
'platform.dart'
;
// The Flutter tool hits file system errors that only the end-user can address.
// We would like these errors to not hit crash logging. In these cases, we
// should exit gracefully and provide potentially useful advice. For example, if
// a write fails because the target device is full, we can explain that with a
// ToolExit and a message that is more clear than the FileSystemException by
// itself.
/// A [FileSystem] that throws a [ToolExit] on certain errors.
///
/// If a [FileSystem] error is not caused by the Flutter tool, and can only be
/// addressed by the user, it should be caught by this [FileSystem] and thrown
/// as a [ToolExit] using [throwToolExit].
///
/// Cf. If there is some hope that the tool can continue when an operation fails
/// with an error, then that error/operation should not be handled here. For
/// example, the tool should gernerally be able to continue executing even if it
/// fails to delete a file.
class
ErrorHandlingFileSystem
extends
ForwardingFileSystem
{
ErrorHandlingFileSystem
(
FileSystem
delegate
)
:
super
(
delegate
);
@visibleForTesting
FileSystem
get
fileSystem
=>
delegate
;
@override
File
file
(
dynamic
path
)
=>
ErrorHandlingFile
(
delegate
,
delegate
.
file
(
path
));
}
class
ErrorHandlingFile
extends
ForwardingFileSystemEntity
<
File
,
io
.
File
>
with
ForwardingFile
{
ErrorHandlingFile
(
this
.
fileSystem
,
this
.
delegate
);
@override
final
io
.
File
delegate
;
@override
final
FileSystem
fileSystem
;
@override
File
wrapFile
(
io
.
File
delegate
)
=>
ErrorHandlingFile
(
fileSystem
,
delegate
);
@override
Directory
wrapDirectory
(
io
.
Directory
delegate
)
=>
ErrorHandlingDirectory
(
fileSystem
,
delegate
);
@override
Link
wrapLink
(
io
.
Link
delegate
)
=>
ErrorHandlingLink
(
fileSystem
,
delegate
);
@override
Future
<
File
>
writeAsBytes
(
List
<
int
>
bytes
,
{
FileMode
mode
=
FileMode
.
write
,
bool
flush
=
false
,
})
async
{
return
_run
<
File
>(
()
async
=>
wrap
(
await
delegate
.
writeAsBytes
(
bytes
,
mode:
mode
,
flush:
flush
,
)),
failureMessage:
'Flutter failed to write to a file at "
${delegate.path}
"'
,
);
}
@override
void
writeAsBytesSync
(
List
<
int
>
bytes
,
{
FileMode
mode
=
FileMode
.
write
,
bool
flush
=
false
,
})
{
_runSync
<
void
>(
()
=>
delegate
.
writeAsBytesSync
(
bytes
,
mode:
mode
,
flush:
flush
),
failureMessage:
'Flutter failed to write to a file at "
${delegate.path}
"'
,
);
}
@override
Future
<
File
>
writeAsString
(
String
contents
,
{
FileMode
mode
=
FileMode
.
write
,
Encoding
encoding
=
utf8
,
bool
flush
=
false
,
})
async
{
return
_run
<
File
>(
()
async
=>
wrap
(
await
delegate
.
writeAsString
(
contents
,
mode:
mode
,
encoding:
encoding
,
flush:
flush
,
)),
failureMessage:
'Flutter failed to write to a file at "
${delegate.path}
"'
,
);
}
@override
void
writeAsStringSync
(
String
contents
,
{
FileMode
mode
=
FileMode
.
write
,
Encoding
encoding
=
utf8
,
bool
flush
=
false
,
})
{
_runSync
<
void
>(
()
=>
delegate
.
writeAsStringSync
(
contents
,
mode:
mode
,
encoding:
encoding
,
flush:
flush
,
),
failureMessage:
'Flutter failed to write to a file at "
${delegate.path}
"'
,
);
}
Future
<
T
>
_run
<
T
>(
Future
<
T
>
Function
()
op
,
{
String
failureMessage
})
async
{
try
{
return
await
op
();
}
on
FileSystemException
catch
(
e
)
{
if
(
platform
.
isWindows
)
{
_handleWindowsException
(
e
,
failureMessage
);
}
rethrow
;
}
}
T
_runSync
<
T
>(
T
Function
()
op
,
{
String
failureMessage
})
{
try
{
return
op
();
}
on
FileSystemException
catch
(
e
)
{
if
(
platform
.
isWindows
)
{
_handleWindowsException
(
e
,
failureMessage
);
}
rethrow
;
}
}
void
_handleWindowsException
(
FileSystemException
e
,
String
message
)
{
// From:
// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes
const
int
kDeviceFull
=
112
;
const
int
kUserMappedSectionOpened
=
1224
;
final
int
errorCode
=
e
.
osError
?.
errorCode
??
0
;
// Catch errors and bail when:
switch
(
errorCode
)
{
case
kDeviceFull:
throwToolExit
(
'
$message
. The target device is full.'
'
\n
$e
\n
'
'Free up space and try again.'
,
);
break
;
case
kUserMappedSectionOpened:
throwToolExit
(
'
$message
. The file is being used by another program.'
'
\n
$e
\n
'
'Do you have an antivirus program running? '
'Try disabling your antivirus program and try again.'
,
);
break
;
default
:
// Caller must rethrow the exception.
break
;
}
}
}
class
ErrorHandlingDirectory
extends
ForwardingFileSystemEntity
<
Directory
,
io
.
Directory
>
with
ForwardingDirectory
<
Directory
>
{
ErrorHandlingDirectory
(
this
.
fileSystem
,
this
.
delegate
);
@override
final
io
.
Directory
delegate
;
@override
final
FileSystem
fileSystem
;
@override
File
wrapFile
(
io
.
File
delegate
)
=>
ErrorHandlingFile
(
fileSystem
,
delegate
);
@override
Directory
wrapDirectory
(
io
.
Directory
delegate
)
=>
ErrorHandlingDirectory
(
fileSystem
,
delegate
);
@override
Link
wrapLink
(
io
.
Link
delegate
)
=>
ErrorHandlingLink
(
fileSystem
,
delegate
);
// For the childEntity methods, we first obtain an instance of the entity
// from the underlying file system, then invoke childEntity() on it, then
// wrap in the ErrorHandling version.
@override
Directory
childDirectory
(
String
basename
)
=>
wrapDirectory
(
fileSystem
.
directory
(
delegate
).
childDirectory
(
basename
));
@override
File
childFile
(
String
basename
)
=>
wrapFile
(
fileSystem
.
directory
(
delegate
).
childFile
(
basename
));
@override
Link
childLink
(
String
basename
)
=>
wrapLink
(
fileSystem
.
directory
(
delegate
).
childLink
(
basename
));
}
class
ErrorHandlingLink
extends
ForwardingFileSystemEntity
<
Link
,
io
.
Link
>
with
ForwardingLink
{
ErrorHandlingLink
(
this
.
fileSystem
,
this
.
delegate
);
@override
final
io
.
Link
delegate
;
@override
final
FileSystem
fileSystem
;
@override
File
wrapFile
(
io
.
File
delegate
)
=>
ErrorHandlingFile
(
fileSystem
,
delegate
);
@override
Directory
wrapDirectory
(
io
.
Directory
delegate
)
=>
ErrorHandlingDirectory
(
fileSystem
,
delegate
);
@override
Link
wrapLink
(
io
.
Link
delegate
)
=>
ErrorHandlingLink
(
fileSystem
,
delegate
);
}
packages/flutter_tools/lib/src/base/file_system.dart
View file @
895ffc80
...
...
@@ -9,6 +9,7 @@ import 'package:meta/meta.dart';
import
'common.dart'
show
throwToolExit
;
import
'context.dart'
;
import
'error_handling_file_system.dart'
;
import
'platform.dart'
;
export
'package:file/file.dart'
;
...
...
@@ -20,7 +21,9 @@ const FileSystem _kLocalFs = LocalFileSystem();
///
/// By default it uses local disk-based implementation. Override this in tests
/// with [MemoryFileSystem].
FileSystem
get
fs
=>
context
.
get
<
FileSystem
>()
??
_kLocalFs
;
FileSystem
get
fs
=>
ErrorHandlingFileSystem
(
context
.
get
<
FileSystem
>()
??
_kLocalFs
,
);
/// Create the ancestor directories of a file path if they do not already exist.
void
ensureDirectoryExists
(
String
filePath
)
{
...
...
packages/flutter_tools/test/commands.shard/hermetic/precache_test.dart
View file @
895ffc80
...
...
@@ -174,6 +174,7 @@ void main() {
FlutterVersion:
()
=>
flutterVersion
,
FeatureFlags:
()
=>
TestFeatureFlags
(
isWebEnabled:
false
),
});
testUsingContext
(
'precache downloads artifacts when --force is provided'
,
()
async
{
when
(
cache
.
isUpToDate
()).
thenReturn
(
true
);
final
PrecacheCommand
command
=
PrecacheCommand
();
...
...
packages/flutter_tools/test/general.shard/base/error_handling_file_system_test.dart
0 → 100644
View file @
895ffc80
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:file/file.dart'
;
import
'package:flutter_tools/src/base/error_handling_file_system.dart'
;
import
'package:mockito/mockito.dart'
;
import
'package:platform/platform.dart'
;
import
'../../src/common.dart'
;
import
'../../src/context.dart'
;
import
'../../src/testbed.dart'
;
class
MockFile
extends
Mock
implements
File
{}
class
MockFileSystem
extends
Mock
implements
FileSystem
{}
class
MockPlatform
extends
Mock
implements
Platform
{}
void
main
(
)
{
group
(
'throws ToolExit on Windows'
,
()
{
const
int
kDeviceFull
=
112
;
const
int
kUserMappedSectionOpened
=
1224
;
Testbed
testbed
;
MockFileSystem
mockFileSystem
;
MockPlatform
windowsPlatform
;
ErrorHandlingFileSystem
fs
;
setUp
(()
{
mockFileSystem
=
MockFileSystem
();
fs
=
ErrorHandlingFileSystem
(
mockFileSystem
);
windowsPlatform
=
MockPlatform
();
when
(
windowsPlatform
.
isWindows
).
thenReturn
(
true
);
when
(
windowsPlatform
.
isLinux
).
thenReturn
(
false
);
when
(
windowsPlatform
.
isMacOS
).
thenReturn
(
false
);
testbed
=
Testbed
(
overrides:
<
Type
,
Generator
>{
Platform:
()
=>
windowsPlatform
,
});
});
void
writeTests
({
String
testName
,
int
errorCode
,
String
expectedMessage
,
})
{
test
(
testName
,
()
=>
testbed
.
run
(()
async
{
final
MockFile
mockFile
=
MockFile
();
when
(
mockFileSystem
.
file
(
any
)).
thenReturn
(
mockFile
);
when
(
mockFile
.
writeAsBytes
(
any
,
mode:
anyNamed
(
'mode'
),
flush:
anyNamed
(
'flush'
),
)).
thenAnswer
((
_
)
async
{
throw
FileSystemException
(
''
,
''
,
OSError
(
''
,
errorCode
));
});
when
(
mockFile
.
writeAsString
(
any
,
mode:
anyNamed
(
'mode'
),
encoding:
anyNamed
(
'encoding'
),
flush:
anyNamed
(
'flush'
),
)).
thenAnswer
((
_
)
async
{
throw
FileSystemException
(
''
,
''
,
OSError
(
''
,
errorCode
));
});
when
(
mockFile
.
writeAsBytesSync
(
any
,
mode:
anyNamed
(
'mode'
),
flush:
anyNamed
(
'flush'
),
)).
thenThrow
(
FileSystemException
(
''
,
''
,
OSError
(
''
,
errorCode
)));
when
(
mockFile
.
writeAsStringSync
(
any
,
mode:
anyNamed
(
'mode'
),
encoding:
anyNamed
(
'encoding'
),
flush:
anyNamed
(
'flush'
),
)).
thenThrow
(
FileSystemException
(
''
,
''
,
OSError
(
''
,
errorCode
)));
final
File
file
=
fs
.
file
(
'file'
);
expect
(()
async
=>
await
file
.
writeAsBytes
(<
int
>[
0
]),
throwsToolExit
(
message:
expectedMessage
));
expect
(()
async
=>
await
file
.
writeAsString
(
''
),
throwsToolExit
(
message:
expectedMessage
));
expect
(()
=>
file
.
writeAsBytesSync
(<
int
>[
0
]),
throwsToolExit
(
message:
expectedMessage
));
expect
(()
=>
file
.
writeAsStringSync
(
''
),
throwsToolExit
(
message:
expectedMessage
));
}));
}
writeTests
(
testName:
'when writing to a full device'
,
errorCode:
kDeviceFull
,
expectedMessage:
'The target device is full'
,
);
writeTests
(
testName:
'when the file is being used by another program'
,
errorCode:
kUserMappedSectionOpened
,
expectedMessage:
'The file is being used by another program'
,
);
});
}
packages/flutter_tools/test/general.shard/forbidden_imports_test.dart
View file @
895ffc80
...
...
@@ -39,6 +39,7 @@ void main() {
test
(
'no unauthorized imports of dart:io'
,
()
{
final
List
<
String
>
whitelistedPaths
=
<
String
>[
fs
.
path
.
join
(
flutterTools
,
'lib'
,
'src'
,
'base'
,
'io.dart'
),
fs
.
path
.
join
(
flutterTools
,
'lib'
,
'src'
,
'base'
,
'error_handling_file_system.dart'
),
];
bool
_isNotWhitelisted
(
FileSystemEntity
entity
)
=>
whitelistedPaths
.
every
((
String
path
)
=>
path
!=
entity
.
path
);
...
...
@@ -83,6 +84,7 @@ void main() {
test
(
'no unauthorized imports of dart:convert'
,
()
{
final
List
<
String
>
whitelistedPaths
=
<
String
>[
fs
.
path
.
join
(
flutterTools
,
'lib'
,
'src'
,
'convert.dart'
),
fs
.
path
.
join
(
flutterTools
,
'lib'
,
'src'
,
'base'
,
'error_handling_file_system.dart'
),
];
bool
_isNotWhitelisted
(
FileSystemEntity
entity
)
=>
whitelistedPaths
.
every
((
String
path
)
=>
path
!=
entity
.
path
);
...
...
packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart
View file @
895ffc80
...
...
@@ -6,6 +6,8 @@ import 'dart:async';
import
'dart:io'
as
io
;
import
'package:flutter_tools/src/base/common.dart'
;
import
'package:flutter_tools/src/base/error_handling_file_system.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/base/io.dart'
;
import
'package:flutter_tools/src/base/signals.dart'
;
import
'package:flutter_tools/src/base/time.dart'
;
...
...
@@ -65,6 +67,16 @@ void main() {
Cache:
()
=>
cache
,
});
testUsingContext
(
'uses the error handling file system'
,
()
async
{
final
DummyFlutterCommand
flutterCommand
=
DummyFlutterCommand
(
commandFunction:
()
async
{
expect
(
fs
,
isA
<
ErrorHandlingFileSystem
>());
return
const
FlutterCommandResult
(
ExitStatus
.
success
);
}
);
await
flutterCommand
.
run
();
});
void
testUsingCommandContext
(
String
testName
,
dynamic
Function
()
testBody
)
{
testUsingContext
(
testName
,
testBody
,
overrides:
<
Type
,
Generator
>{
ProcessInfo:
()
=>
mockProcessInfo
,
...
...
packages/flutter_tools/test/general.shard/testbed_test.dart
View file @
895ffc80
...
...
@@ -5,9 +5,11 @@
import
'dart:async'
;
import
'dart:io'
;
import
'package:file/file.dart'
;
import
'package:file/memory.dart'
;
import
'package:flutter_tools/src/base/context.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/base/error_handling_file_system.dart'
;
import
'../src/common.dart'
;
import
'../src/testbed.dart'
;
...
...
@@ -23,7 +25,9 @@ void main() {
localFileSystem
=
fs
;
});
expect
(
localFileSystem
,
isA
<
MemoryFileSystem
>());
expect
(
localFileSystem
,
isA
<
ErrorHandlingFileSystem
>());
expect
((
localFileSystem
as
ErrorHandlingFileSystem
).
fileSystem
,
isA
<
MemoryFileSystem
>());
});
test
(
'Can provide setup interfaces'
,
()
async
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment