The function node_save() catches exceptions now in Drupal 7.
http://api.drupal.org/api/function/node_save/7
This means that in DrupalWebTestCase::drupalCreateNode(), it doesn't necessarily notice when a node hasn't actually been created. It still returns a valid-looking node object, even though the node hasn't been created. That seems like a really bad idea to me in a test, and makes debugging the test difficult, because it should have failed at drupalCreateNode, but instead you don't see failures until later on when you try to do something with the node.
I would propose doing a node_load() in drupalCreateNode after node_save, and returning the result of the node_load. And also throwing an error if the node_load() fails. This would allow drupalCreateNode() to catch problems in creation of nodes.
Comment | File | Size | Author |
---|---|---|---|
#35 | 704646-34.patch | 335 bytes | jpmckinney |
#43 | 704646-41.patch | 2.67 KB | mradcliffe |
#40 | 704646-40.patch | 2.36 KB | jpmckinney |
#38 | 704646-36.patch | 1.5 KB | jpmckinney |
#34 | 704646-34.patch | 335 bytes | jpmckinney |
Comments
Comment #1
jhodgdonActually, shouldn't node_save() do some kind of drupal_set_message to tell you that node creation failed in the UI?
Comment #2
jhodgdonI'm changing this to being a bug in the node module. This is totally wrong...
I tested this in the UI, by enabling the test module that I am trying to debug, which is currently preventing the creation of all node content due to some bug I haven't found yet.
I went to the Create Content page, and typed in some random text for title (I typed in "asdf") and body, and clicked "save".
Here's what I see: a "new asdf has been created", and a 404 error (normally it would take me to the page I just created, but it wasn't created due to my module having a problem).
This seems totally wrong...
Comment #3
jhodgdonHere's a screen shot of the dblog for this.
There's an error from node (that's the error that caused the node not to be saved).
Then there's a notice from "content" saying that "asdf" has beed added, which is not actually true.
Then there's the page not found error for node/2, correctly stating that the page cannot be found.
Anyway, it seems to me that there's a problem if node_save doesn't report to its calling function or to the UI that an error occurred. And DrupalWebTestCase::drupalCreateNode() also needs to be aware that node_save didn't work, for testing purposes.
Comment #4
moshe weitzman CreditAttribution: moshe weitzman commentedAPI functions like node_save() don't set messages or issue watchdog and so on. thats the responsibility of the caller.
Comment #5
jhodgdonSure. The caller (in this case node.module) doesn't do the right thing then.
But node_save() also doesn't really give any indication to the caller about whether it succeeded or not.
Comment #6
catchIt could do $status = drupal_write_record($node); then return $status at the end, that gives you 1 or 2 depending on whether it's an insert or update and would be consistent with taxonomy_term_save() and possibly others. The exception watchdog message ought to be caught by test bot but I think there's an outstanding patch for that.
Comment #7
jhodgdonThere are three problems here, as I see it:
a) node_save() doesn't report failure/exception except to dblog. Should probably have a return value indicating success/failure in some way, and if possible, probably returning the error message.
b) In the regular Drupal UI, if there's a failure when saving a node, the UI doesn't tell you anything about it (see screen shot in #2 above).
c) In DrupalWebTestCase::drupalCreateNode, it needs to report a test failure if the node is not created.
If (a) is fixed, it will facilitate fixing (b) and (c).
(b) is critical for UI at release, IMO.
(c) is critical for testing, IMO.
Comment #8
catchAgreed this is critical, just eating errors and writing them to dblog is a recipe for hard to find bugs all over the place, and a serious regression from D6.
Comment #9
cha0s CreditAttribution: cha0s commentedIMO, the exception should be propagated up, that is if node_save wants to catch it and write to watchdog then fine, but I think it should re-throw the exception is has caught. I am currently unaware of the scope of changes this would require in calling code.
Comment #10
willmoy CreditAttribution: willmoy commentedNODE_SAVE() RETURN VALUE
As catch said, node_save() should return FALSE if any of its drupal_write_record() calls return FALSE.
Same applies to _node_save_revision() and therefore _node_save_revision needs to return FALSE on failure, based on its drupal_write_record() return.
I'm not qualified to judge whether re-throwing the exception makes sense but it would allow the UI to give more useful error messages.
UI
The good news is that only 5 functions call node_save:
- book_admin_edit_submit in modules/book/book.admin.inc
- node_form_submit in modules/node/node.pages.inc
- node_revision_revert_confirm_submit in modules/node/node.pages.inc
- node_save_action in modules/node/node.module
- _node_mass_update_helper in modules/node/node.admin.inc
http://api.drupal.org/api/function/node_save/7 (I love that site)
So fixing the UI actually reporting failure shouldn't be too hard.
TESTS
Testing does handle exceptions, apparently:
#305077: Rework simpletest backend solved #293521: Simpletest should catch exceptions and #242069: Simpletest should include exception data in reporting so this should follow naturally. Also #304924: Extend drupal_error_handler to manage exceptions
Comment #11
willmoy CreditAttribution: willmoy commentedThis patch doesn't fiddle with exceptions because it doesn't seem necessary for the actual calling functions. Provides a return status for node_save() and _node_save_revision() and implements what seem to me to be reasonable responses for the callers.
UI:
- book_admin_edit_submit in modules/book/book.admin.inc - report failure to change title but carry on with ordering and other nodes
- node_form_submit in modules/node/node.pages.inc - report failure
- node_revision_revert_confirm_submit in modules/node/node.pages.inc - report failure
- node_save_action in modules/node/node.module - return status, which actions_do passes to its calling function. These don't appear to take any notice, but that's not our problem here.
- _node_mass_update_helper in modules/node/node.admin.inc - made it take a node rather than a nid, and return the status. Calling functions then drupal_set_message() if there's an error. This implies a lot of drupal_set_messages() but is very unlikely to happen, I'd have thought.
Comment #12
moshe weitzman CreditAttribution: moshe weitzman commentedthe watchdog() should be in an else(), no?
Otherwise, looks good.
Powered by Dreditor.
Comment #13
willmoy CreditAttribution: willmoy commentedYes.
Comment #14
cha0s CreditAttribution: cha0s commentedRerolled because '*=' is the wrong operator to use with boolean logic. Use '&=' instead.
Also, I must admit I think this is much worse than throwing relevant exceptions at failure points, instead of just aggregating a boolean state by accumulating failures over the course of the procedure. At very least we should be dropping out early on the first fail. It's crazy that the function is failing on the first $return = FALSE, but yet doing like 85 other things between there and the return.
Putting in my vote for throwing exceptions, but on the chance that no one really listens or cares, here's the rerolled patch to use the proper operators for this... approach.
Comment #16
cha0s CreditAttribution: cha0s commentedOh, I see why. Can't do &&=, silly me. Maybe *= is better here? Seems wrong to use arithmetic to get boolean answers. I appreciate 'clever hax' as much as the next guy but it at least warrants a comment if we keep this, yeah?
Comment #17
willmoy CreditAttribution: willmoy commentedPrints
int(0) int(0)
. No difference, but no boolean either. No idea, sorry.Comment #18
cha0s CreditAttribution: cha0s commentedI think I know why... it's because the rvalue in those expr's don't necessarily equal 1.
1 & 2 = 0
:)Comment #19
jhodgdonThen how about just
$foo = $foo && TRUE;
That is guaranteed to work. No need to use a *= or &= shorthand that is not guaranteed to work?
Comment #20
jhodgdonOther comments on the patches above in #13 and #14:
a) Bad indentation and extra spaces on that second line after the = sign:
b) Function header definitely needs doc:
/**
* Node Mass Update - helper function.
*/
-function _node_mass_update_helper($nid, $updates) {
- $node = node_load($nid, NULL, TRUE);
+function _node_mass_update_helper(&$node, $updates) {
c) Given this new function def, shouldn't this function be passing in $node not $nid? How could this have been working at all? There must be a missing test...
d) Needs a "." after failure:
e) I definitely agree that as soon as any kind of failure is detected, node_save() should abort and return. And the
$return &= or $return *= lines should just go away. But if you do go that route, it should be $return = $return &&.
f) Shouldn't catching an exception also cause $return to be FALSE?
g) We are not using the words "post" and "node" in Drupal 6 UI. Please call it "content item".
Comment #21
cha0s CreditAttribution: cha0s commentedYo, got a new patch here.
Addresses the things jhodgdon mentioned, and also makes more sense, logically.
Instead of just plowing over stuff when there's a fail (current version) or just tiptoeing over sensitive stuff once there's a fail ($return = $return &&), I've implemented exceptions for each of the failure points.
Now, if anything fails, an exception is thrown, which jumps right over anything else and right to the transaction rollback (Something that wasn't handled on non-exception-throwing failures), and allow us to mark the return as FALSE from the catch block, instead of having it all over the place.
Comment #22
jhodgdonThe code is MUCH better! A couple of things:
a) This is not going to work:
The _node_mass_update_helper function takes $node not $nid as its first parameter.
b) Because of (a) and the tests passing, there must not be a test that exercises _node_mass_update_batch_process. There should be.
c) If you're fixing things anyway:
Why not just return FALSE inside the catch, and return TRUE at the end? There is really no need for the $return variable in your new code.
Comment #23
cha0s CreditAttribution: cha0s commentedGood call on the return variable. I also fixed the batch update function, and added a test.
Oh, and I also added a check for node_save in the SimpleTest system.
Let's see what zee bot thinks.
Comment #25
cha0s CreditAttribution: cha0s commentedAh, I invoked the batch function incorrectly from the test.
Comment #26
jhodgdonThis patch is closer...
- I would like to see a test added that makes node_save() fail and verifies that the error message is shown. It could use a test class that somehow makes node creation fail in one of the Node API hooks (for example, try to update a database table that doesn't exist or do a query with a syntax error in it), and verify that the correct error message is generated from the path node/add/[type] when it can't create the node.
- I think the test for _node_mass_update_batch_process() should be calling the public API function node_mass_update() instead of calling the internal helper function directly.
- I also think the message "'Node could not be created for drupalCreateNode()." in drupalCreateNode should probably just say "Node could not be created". None of the other messages in DrupalWebTestCase are formatted like that, and the test results page does tell you the function name and line number anyway.
- Also, drupalCreateNode() should not return a node object if it fails, which is what it was doing before, and this patch doesn't change that.
Comment #27
casey CreditAttribution: casey commentedSubscribing.
Comment #28
BerdirThe %type.. sentence doesn't make much to me. Do we even need that one?
I see why it is necessary, but this seems like an API change. Yes, it's probably necessary and it's an internal helper function, Just saying.. :)
Same as above
Imho, Exception shouldn't be used directly but something like NodeSaveException instead. Also, the strings can be improved. It looks strange that they aren't translated but that's because they are passed to watchdog and translated when displayed. Quite a mess imho, but nothing we can do about it...
Agreed that we dont the function name in the message and can't we just use $this->assertTrue(node_save($node), .. here?
Powered by Dreditor.
Comment #29
jhodgdonRegarding Berdir's first comment, I actually think the error message is pretty good. It would read something like:
"An error occurred while processing My Node Title. The Page cannot be saved."
I think we do need to tell them that it wasn't saved, and this seems like a clear way to do it.
Comment #30
BerdirHm, maybe :) But note that $node->type is the machine readable name....
Comment #31
jhodgdonGood point! The message should be changed to retrieve the human-readable name of the type, just as the message would if the save had been successful.
Comment #32
cha0s CreditAttribution: cha0s commentedJust wanna bump this to the top of my queue.
Comment #33
catchPer #9 we should just re-throw the exception, bloating the calling code like this just in case there's a critical error seems unreasonable to me. If there's an exception you want a big red error, not a nice message saying sorry.
Comment #34
jpmckinney CreditAttribution: jpmckinney commentedI agree. We should not be using exceptions half-way. Exceptions were introduced to programming specifically to avoid handling and throwing return values of FALSE and TRUE all the way up the call chain. Here is a patch that re-throws exceptions, addressing the issues in the OP and in #7.
Comment #35
jpmckinney CreditAttribution: jpmckinney commentedComment #36
jpmckinney CreditAttribution: jpmckinney commentedChanging title to not presume handling exceptions is the right choice.
Comment #38
jpmckinney CreditAttribution: jpmckinney commentedUpdate test to expect exception.
Comment #40
jpmckinney CreditAttribution: jpmckinney commentedI think I need to be calling node_save() instead of drupalPost in this test, as otherwise I can't catch the exception thrown by node_test_exception_node_insert.
Comment #41
jpmckinney CreditAttribution: jpmckinney commentedComment #43
mradcliffere-rolled as a cvs diff patch.
Comment #44
catchI didn't even know hook_node_post_save() existed seems quite useless given _insert() and fapi both exist, we should get rid of that in D8. Patch looks good, tests pass.
Comment #45
jhodgdonhook_node_post_save doesn't exist. That's a - line in the patch.
ADDED: See http://api.drupal.org/api/group/node_api_hooks/7 for a list of the actual hooks (hopefully it's still up to date). Whether they're all needed or not is open to question...
Comment #46
catchOh I see, just the existing comment is wrong talking about a hook which doesn't exist, should've realised since there's no change to the dummy module itself. Good cleanup either way.
Comment #47
Dries CreditAttribution: Dries commentedCommitted to CVS HEAD -- glad to see it was synced with comment_save() fails silently.
Comment #49
glacialheart CreditAttribution: glacialheart commentedWas this ever fixed for D6? I'm having this exact issue with node_save() on a D6 site. I checked the {watchdog} table and found no errors. I am at a loss to identify what the error is. Each time I call node_save() the nid gets incremented yet no new node is added to the table.
Comment #50
BerdirThis doesn't affect Drupal 6. The problem here was that the exceptions was thrown and there was no way to catch it. There are no exceptions used in D6. Make sure that you have error messages enabled.
Comment #51
glacialheart CreditAttribution: glacialheart commentedI have messages enabled. No messages are displayed. I get a message when I create a node/add/story but nothing when I use node_save(). (I don't want to hijack this thread, should I start a new one? But, I'm not sure that I have a real issue)
Comment #52
Ciprian Oancia CreditAttribution: Ciprian Oancia commentedHi everybody
in my case i need for several content types that the "the content x has been created" doesn't display other i will have a button for the next step. Is there any way i can disable this confirmation?
thank you